From f6a95da536dbed3775df7d753bf8ac03d8c86043 Mon Sep 17 00:00:00 2001 From: Ryan O'Hara-Reid Date: Mon, 3 Jun 2024 11:57:31 +1000 Subject: [PATCH 1/7] exchanges/request: abstract and consolidate rate limiting code to request package (#1477) * initial consolidation of rate limiting code to request package to reduce bespoke code implementation * continued * finish abstraction * lint * exchanges: fix tests * linter: fix * poloniex: fix auth rate limit not being set * ratelimiter: convert from token to weight * glorious: nits addressed with fire * linter: rip * change func name set -> get * fix test * derbit: impl --------- Co-authored-by: Ryan O'Hara-Reid --- cmd/apichecker/apicheck.go | 6 +- currency/coinmarketcap/coinmarketcap.go | 2 +- .../currencyconverterapi.go | 2 +- .../exchangeratesapi.io/exchangeratesapi.go | 2 +- exchanges/binance/binance_cfutures.go | 2 +- exchanges/binance/binance_types.go | 5 - exchanges/binance/binance_wrapper.go | 2 +- exchanges/binance/ratelimit.go | 242 ++--- exchanges/binance/ratelimit_test.go | 38 +- exchanges/binance/type_convert.go | 22 - exchanges/binanceus/binanceus_wrapper.go | 2 +- exchanges/binanceus/ratelimit.go | 99 +-- exchanges/bitfinex/bitfinex_wrapper.go | 2 +- exchanges/bitfinex/ratelimit.go | 383 ++------ exchanges/bitflyer/bitflyer.go | 2 +- exchanges/bitflyer/bitflyer_wrapper.go | 2 +- exchanges/bitflyer/ratelimit.go | 56 +- exchanges/bithumb/bithumb_wrapper.go | 2 +- exchanges/bithumb/ratelimit.go | 26 +- exchanges/bitmex/bitmex.go | 2 +- exchanges/bitmex/bitmex_wrapper.go | 2 +- exchanges/bitmex/ratelimit.go | 26 +- exchanges/bitstamp/bitstamp_wrapper.go | 2 +- exchanges/btcmarkets/btcmarkets.go | 2 +- exchanges/btcmarkets/btcmarkets_wrapper.go | 2 +- exchanges/btcmarkets/ratelimit.go | 48 +- exchanges/btse/btse_wrapper.go | 2 +- exchanges/btse/ratelimit.go | 28 +- exchanges/bybit/bybit_wrapper.go | 2 +- exchanges/bybit/ratelimit.go | 346 ++------ exchanges/coinbasepro/coinbasepro.go | 2 +- exchanges/coinbasepro/coinbasepro_wrapper.go | 2 +- exchanges/coinbasepro/ratelimit.go | 26 +- exchanges/deribit/deribit_wrapper.go | 2 +- exchanges/deribit/ratelimit.go | 71 +- exchanges/exmo/exmo_wrapper.go | 2 +- exchanges/gateio/gateio_wrapper.go | 2 +- exchanges/gateio/ratelimiter.go | 90 +- exchanges/gemini/gemini.go | 2 +- exchanges/gemini/gemini_wrapper.go | 2 +- exchanges/gemini/ratelimit.go | 26 +- exchanges/hitbtc/hitbtc_wrapper.go | 2 +- exchanges/hitbtc/ratelimit.go | 36 +- exchanges/huobi/huobi_wrapper.go | 2 +- exchanges/huobi/ratelimit.go | 52 +- exchanges/kraken/kraken_wrapper.go | 2 +- exchanges/kucoin/kucoin_ratelimit.go | 187 +--- exchanges/kucoin/kucoin_wrapper.go | 2 +- exchanges/okcoin/okcoin_ratelimit.go | 366 ++------ exchanges/okcoin/okcoin_wrapper.go | 2 +- exchanges/okx/okx.go | 6 +- exchanges/okx/okx_test.go | 4 +- exchanges/okx/okx_websocket.go | 6 - exchanges/okx/okx_wrapper.go | 16 +- exchanges/okx/ratelimit.go | 838 ++++-------------- exchanges/poloniex/poloniex.go | 4 +- exchanges/poloniex/poloniex_wrapper.go | 2 +- exchanges/poloniex/ratelimit.go | 26 +- exchanges/request/limit.go | 106 ++- exchanges/request/options.go | 4 +- exchanges/request/request.go | 20 +- exchanges/request/request_test.go | 84 +- exchanges/request/request_types.go | 2 +- exchanges/yobit/yobit_wrapper.go | 2 +- 64 files changed, 780 insertions(+), 2577 deletions(-) diff --git a/cmd/apichecker/apicheck.go b/cmd/apichecker/apicheck.go index 8bf4594f1b6..38417156c86 100644 --- a/cmd/apichecker/apicheck.go +++ b/cmd/apichecker/apicheck.go @@ -1226,11 +1226,11 @@ func sendGetReq(path string, result interface{}) error { if strings.Contains(path, "github") { requester, err = request.New("Apichecker", common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout), - request.WithLimiter(request.NewBasicRateLimit(time.Hour, 60))) + request.WithLimiter(request.NewBasicRateLimit(time.Hour, 60, 1))) } else { requester, err = request.New("Apichecker", common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout), - request.WithLimiter(request.NewBasicRateLimit(time.Second, 100))) + request.WithLimiter(request.NewBasicRateLimit(time.Second, 100, 1))) } if err != nil { return err @@ -1249,7 +1249,7 @@ func sendGetReq(path string, result interface{}) error { func sendAuthReq(method, path string, result interface{}) error { requester, err := request.New("Apichecker", common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout), - request.WithLimiter(request.NewBasicRateLimit(time.Second*10, 100))) + request.WithLimiter(request.NewBasicRateLimit(time.Second*10, 100, 1))) if err != nil { return err } diff --git a/currency/coinmarketcap/coinmarketcap.go b/currency/coinmarketcap/coinmarketcap.go index ec8a77e2591..ee45fcc8929 100644 --- a/currency/coinmarketcap/coinmarketcap.go +++ b/currency/coinmarketcap/coinmarketcap.go @@ -39,7 +39,7 @@ func (c *Coinmarketcap) SetDefaults() { var err error c.Requester, err = request.New(c.Name, common.NewHTTPClientWithTimeout(defaultTimeOut), - request.WithLimiter(request.NewBasicRateLimit(RateInterval, BasicRequestRate)), + request.WithLimiter(request.NewBasicRateLimit(RateInterval, BasicRequestRate, 1)), ) if err != nil { log.Errorln(log.Global, err) diff --git a/currency/forexprovider/currencyconverterapi/currencyconverterapi.go b/currency/forexprovider/currencyconverterapi/currencyconverterapi.go index f1a1f8b0418..afc994798cd 100644 --- a/currency/forexprovider/currencyconverterapi/currencyconverterapi.go +++ b/currency/forexprovider/currencyconverterapi/currencyconverterapi.go @@ -26,7 +26,7 @@ func (c *CurrencyConverter) Setup(config base.Settings) error { var err error c.Requester, err = request.New(c.Name, common.NewHTTPClientWithTimeout(base.DefaultTimeOut), - request.WithLimiter(request.NewBasicRateLimit(rateInterval, requestRate))) + request.WithLimiter(request.NewBasicRateLimit(rateInterval, requestRate, 1))) return err } diff --git a/currency/forexprovider/exchangeratesapi.io/exchangeratesapi.go b/currency/forexprovider/exchangeratesapi.io/exchangeratesapi.go index d4c853de7a9..1890caab948 100644 --- a/currency/forexprovider/exchangeratesapi.io/exchangeratesapi.go +++ b/currency/forexprovider/exchangeratesapi.io/exchangeratesapi.go @@ -32,7 +32,7 @@ func (e *ExchangeRates) Setup(config base.Settings) error { var err error e.Requester, err = request.New(e.Name, common.NewHTTPClientWithTimeout(base.DefaultTimeOut), - request.WithLimiter(request.NewBasicRateLimit(rateLimitInterval, requestRate))) + request.WithLimiter(request.NewBasicRateLimit(rateLimitInterval, requestRate, 1))) return err } diff --git a/exchanges/binance/binance_cfutures.go b/exchanges/binance/binance_cfutures.go index 92cd3120894..ac09e6ada8d 100644 --- a/exchanges/binance/binance_cfutures.go +++ b/exchanges/binance/binance_cfutures.go @@ -722,7 +722,7 @@ func getKlineRateBudget(limit int64) request.EndpointLimit { rateBudget := cFuturesDefaultRate switch { case limit > 0 && limit < 100: - rateBudget = cFuturesKline100Rate + rateBudget = cFuturesDefaultRate case limit >= 100 && limit < 500: rateBudget = cFuturesKline500Rate case limit >= 500 && limit < 1000: diff --git a/exchanges/binance/binance_types.go b/exchanges/binance/binance_types.go index ceb5316d2cf..f21206ad1f5 100644 --- a/exchanges/binance/binance_types.go +++ b/exchanges/binance/binance_types.go @@ -753,11 +753,6 @@ type UserAccountStream struct { ListenKey string `json:"listenKey"` } -type wsAccountInfo struct { - Stream string `json:"stream"` - Data WsAccountInfoData `json:"data"` -} - // WsAccountInfoData defines websocket account info data type WsAccountInfoData struct { CanDeposit bool `json:"D"` diff --git a/exchanges/binance/binance_wrapper.go b/exchanges/binance/binance_wrapper.go index cf59559e537..4c15f27ed00 100644 --- a/exchanges/binance/binance_wrapper.go +++ b/exchanges/binance/binance_wrapper.go @@ -198,7 +198,7 @@ func (b *Binance) SetDefaults() { b.Requester, err = request.New(b.Name, common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout), - request.WithLimiter(SetRateLimit())) + request.WithLimiter(GetRateLimits())) if err != nil { log.Errorln(log.ExchangeSys, err) } diff --git a/exchanges/binance/ratelimit.go b/exchanges/binance/ratelimit.go index c56f16038ca..616a084a948 100644 --- a/exchanges/binance/ratelimit.go +++ b/exchanges/binance/ratelimit.go @@ -1,12 +1,9 @@ package binance import ( - "context" - "fmt" "time" "github.com/thrasher-corp/gocryptotrader/exchanges/request" - "golang.org/x/time/rate" ) const ( @@ -77,7 +74,6 @@ const ( cFuturesOrderbook100Rate cFuturesOrderbook500Rate cFuturesOrderbook1000Rate - cFuturesKline100Rate cFuturesKline500Rate cFuturesKline1000Rate cFuturesKlineMaxRate @@ -96,173 +92,79 @@ const ( uFuturesSetMultiAssetMarginRate ) -// RateLimit implements the request.Limiter interface -type RateLimit struct { - SpotRate *rate.Limiter - SpotOrdersRate *rate.Limiter - UFuturesRate *rate.Limiter - UFuturesOrdersRate *rate.Limiter - CFuturesRate *rate.Limiter - CFuturesOrdersRate *rate.Limiter -} - -// Limit executes rate limiting functionality for Binance -func (r *RateLimit) Limit(ctx context.Context, f request.EndpointLimit) error { - var limiter *rate.Limiter - var tokens int - switch f { - case spotDefaultRate: - limiter, tokens = r.SpotRate, 1 - case spotOrderbookTickerAllRate, - spotSymbolPriceAllRate: - limiter, tokens = r.SpotRate, 2 - case spotHistoricalTradesRate, - spotOrderbookDepth500Rate: - limiter, tokens = r.SpotRate, 5 - case spotOrderbookDepth1000Rate, - spotAccountInformationRate, - spotExchangeInfo: - limiter, tokens = r.SpotRate, 10 - case spotPriceChangeAllRate: - limiter, tokens = r.SpotRate, 40 - case spotOrderbookDepth5000Rate: - limiter, tokens = r.SpotRate, 50 - case spotOrderRate: - limiter, tokens = r.SpotOrdersRate, 1 - case spotOrderQueryRate: - limiter, tokens = r.SpotOrdersRate, 2 - case spotOpenOrdersSpecificRate: - limiter, tokens = r.SpotOrdersRate, 3 - case spotAllOrdersRate: - limiter, tokens = r.SpotOrdersRate, 10 - case spotOpenOrdersAllRate: - limiter, tokens = r.SpotOrdersRate, 40 - case uFuturesDefaultRate, - uFuturesKline100Rate: - limiter, tokens = r.UFuturesRate, 1 - case uFuturesOrderbook50Rate, - uFuturesKline500Rate, - uFuturesOrderbookTickerAllRate: - limiter, tokens = r.UFuturesRate, 2 - case uFuturesOrderbook100Rate, - uFuturesKline1000Rate, - uFuturesAccountInformationRate: - limiter, tokens = r.UFuturesRate, 5 - case uFuturesOrderbook500Rate, - uFuturesKlineMaxRate: - limiter, tokens = r.UFuturesRate, 10 - case uFuturesOrderbook1000Rate, - uFuturesHistoricalTradesRate: - limiter, tokens = r.UFuturesRate, 20 - case uFuturesTickerPriceHistoryRate: - limiter, tokens = r.UFuturesRate, 40 - case uFuturesOrdersDefaultRate: - limiter, tokens = r.UFuturesOrdersRate, 1 - case uFuturesBatchOrdersRate, - uFuturesGetAllOrdersRate: - limiter, tokens = r.UFuturesOrdersRate, 5 - case uFuturesCountdownCancelRate: - limiter, tokens = r.UFuturesOrdersRate, 10 - case uFuturesCurrencyForceOrdersRate, - uFuturesSymbolOrdersRate: - limiter, tokens = r.UFuturesOrdersRate, 20 - case uFuturesIncomeHistoryRate: - limiter, tokens = r.UFuturesOrdersRate, 30 - case uFuturesPairOrdersRate, - uFuturesGetAllOpenOrdersRate: - limiter, tokens = r.UFuturesOrdersRate, 40 - case uFuturesAllForceOrdersRate: - limiter, tokens = r.UFuturesOrdersRate, 50 - case cFuturesKline100Rate: - limiter, tokens = r.CFuturesRate, 1 - case cFuturesKline500Rate, - cFuturesOrderbookTickerAllRate: - limiter, tokens = r.CFuturesRate, 2 - case cFuturesKline1000Rate, - cFuturesAccountInformationRate: - limiter, tokens = r.CFuturesRate, 5 - case cFuturesKlineMaxRate, - cFuturesIndexMarkPriceRate: - limiter, tokens = r.CFuturesRate, 10 - case cFuturesHistoricalTradesRate, - cFuturesCurrencyForceOrdersRate: - limiter, tokens = r.CFuturesRate, 20 - case cFuturesTickerPriceHistoryRate: - limiter, tokens = r.CFuturesRate, 40 - case cFuturesAllForceOrdersRate: - limiter, tokens = r.CFuturesRate, 50 - case cFuturesOrdersDefaultRate: - limiter, tokens = r.CFuturesOrdersRate, 1 - case cFuturesBatchOrdersRate, - cFuturesGetAllOpenOrdersRate: - limiter, tokens = r.CFuturesOrdersRate, 5 - case cFuturesCancelAllOrdersRate: - limiter, tokens = r.CFuturesOrdersRate, 10 - case cFuturesIncomeHistoryRate, - cFuturesSymbolOrdersRate: - limiter, tokens = r.CFuturesOrdersRate, 20 - case cFuturesPairOrdersRate: - limiter, tokens = r.CFuturesOrdersRate, 40 - case cFuturesOrderbook50Rate: - limiter, tokens = r.CFuturesRate, 2 - case cFuturesOrderbook100Rate: - limiter, tokens = r.CFuturesRate, 5 - case cFuturesOrderbook500Rate: - limiter, tokens = r.CFuturesRate, 10 - case cFuturesOrderbook1000Rate: - limiter, tokens = r.CFuturesRate, 20 - case cFuturesDefaultRate: - limiter, tokens = r.CFuturesRate, 1 - case uFuturesMultiAssetMarginRate: - limiter, tokens = r.UFuturesRate, 30 - case uFuturesSetMultiAssetMarginRate: - limiter, tokens = r.UFuturesRate, 1 - default: - limiter, tokens = r.SpotRate, 1 - } - - var finalDelay time.Duration - var reserves = make([]*rate.Reservation, tokens) - for i := 0; i < tokens; i++ { - // Consume tokens 1 at a time as this avoids needing burst capacity in the limiter, - // which would otherwise allow the rate limit to be exceeded over short periods - reserves[i] = limiter.Reserve() - finalDelay = reserves[i].Delay() +// GetRateLimits returns the rate limit for the exchange +func GetRateLimits() request.RateLimitDefinitions { + spotDefaultLimiter := request.NewRateLimit(spotInterval, spotRequestRate) + spotOrderLimiter := request.NewRateLimit(spotOrderInterval, spotOrderRequestRate) + usdMarginedFuturesLimiter := request.NewRateLimit(uFuturesInterval, uFuturesRequestRate) + usdMarginedFuturesOrdersLimiter := request.NewRateLimit(uFuturesOrderInterval, uFuturesOrderRequestRate) + coinMarginedFuturesLimiter := request.NewRateLimit(cFuturesInterval, cFuturesRequestRate) + coinMarginedFuturesOrdersLimiter := request.NewRateLimit(cFuturesOrderInterval, cFuturesOrderRequestRate) + + return request.RateLimitDefinitions{ + spotDefaultRate: request.GetRateLimiterWithWeight(spotDefaultLimiter, 1), + spotOrderbookTickerAllRate: request.GetRateLimiterWithWeight(spotDefaultLimiter, 2), + spotSymbolPriceAllRate: request.GetRateLimiterWithWeight(spotDefaultLimiter, 2), + spotHistoricalTradesRate: request.GetRateLimiterWithWeight(spotDefaultLimiter, 5), + spotOrderbookDepth500Rate: request.GetRateLimiterWithWeight(spotDefaultLimiter, 5), + spotOrderbookDepth1000Rate: request.GetRateLimiterWithWeight(spotDefaultLimiter, 10), + spotAccountInformationRate: request.GetRateLimiterWithWeight(spotDefaultLimiter, 10), + spotExchangeInfo: request.GetRateLimiterWithWeight(spotDefaultLimiter, 10), + spotPriceChangeAllRate: request.GetRateLimiterWithWeight(spotDefaultLimiter, 40), + spotOrderbookDepth5000Rate: request.GetRateLimiterWithWeight(spotDefaultLimiter, 50), + spotOrderRate: request.GetRateLimiterWithWeight(spotOrderLimiter, 1), + spotOrderQueryRate: request.GetRateLimiterWithWeight(spotOrderLimiter, 2), + spotOpenOrdersSpecificRate: request.GetRateLimiterWithWeight(spotOrderLimiter, 3), + spotAllOrdersRate: request.GetRateLimiterWithWeight(spotOrderLimiter, 10), + spotOpenOrdersAllRate: request.GetRateLimiterWithWeight(spotOrderLimiter, 40), + uFuturesDefaultRate: request.GetRateLimiterWithWeight(usdMarginedFuturesLimiter, 1), + uFuturesKline100Rate: request.GetRateLimiterWithWeight(usdMarginedFuturesLimiter, 1), + uFuturesOrderbook50Rate: request.GetRateLimiterWithWeight(usdMarginedFuturesLimiter, 2), + uFuturesKline500Rate: request.GetRateLimiterWithWeight(usdMarginedFuturesLimiter, 2), + uFuturesOrderbookTickerAllRate: request.GetRateLimiterWithWeight(usdMarginedFuturesLimiter, 2), + uFuturesOrderbook100Rate: request.GetRateLimiterWithWeight(usdMarginedFuturesLimiter, 5), + uFuturesKline1000Rate: request.GetRateLimiterWithWeight(usdMarginedFuturesLimiter, 5), + uFuturesAccountInformationRate: request.GetRateLimiterWithWeight(usdMarginedFuturesLimiter, 5), + uFuturesOrderbook500Rate: request.GetRateLimiterWithWeight(usdMarginedFuturesLimiter, 10), + uFuturesKlineMaxRate: request.GetRateLimiterWithWeight(usdMarginedFuturesLimiter, 10), + uFuturesOrderbook1000Rate: request.GetRateLimiterWithWeight(usdMarginedFuturesLimiter, 20), + uFuturesHistoricalTradesRate: request.GetRateLimiterWithWeight(usdMarginedFuturesLimiter, 20), + uFuturesTickerPriceHistoryRate: request.GetRateLimiterWithWeight(usdMarginedFuturesLimiter, 40), + uFuturesOrdersDefaultRate: request.GetRateLimiterWithWeight(usdMarginedFuturesOrdersLimiter, 1), + uFuturesBatchOrdersRate: request.GetRateLimiterWithWeight(usdMarginedFuturesOrdersLimiter, 5), + uFuturesGetAllOrdersRate: request.GetRateLimiterWithWeight(usdMarginedFuturesOrdersLimiter, 5), + uFuturesCountdownCancelRate: request.GetRateLimiterWithWeight(usdMarginedFuturesOrdersLimiter, 10), + uFuturesCurrencyForceOrdersRate: request.GetRateLimiterWithWeight(usdMarginedFuturesOrdersLimiter, 20), + uFuturesSymbolOrdersRate: request.GetRateLimiterWithWeight(usdMarginedFuturesOrdersLimiter, 20), + uFuturesIncomeHistoryRate: request.GetRateLimiterWithWeight(usdMarginedFuturesOrdersLimiter, 30), + uFuturesPairOrdersRate: request.GetRateLimiterWithWeight(usdMarginedFuturesOrdersLimiter, 40), + uFuturesGetAllOpenOrdersRate: request.GetRateLimiterWithWeight(usdMarginedFuturesOrdersLimiter, 40), + uFuturesAllForceOrdersRate: request.GetRateLimiterWithWeight(usdMarginedFuturesOrdersLimiter, 50), + cFuturesDefaultRate: request.GetRateLimiterWithWeight(coinMarginedFuturesLimiter, 1), + cFuturesKline500Rate: request.GetRateLimiterWithWeight(coinMarginedFuturesLimiter, 2), + cFuturesOrderbookTickerAllRate: request.GetRateLimiterWithWeight(coinMarginedFuturesLimiter, 2), + cFuturesKline1000Rate: request.GetRateLimiterWithWeight(coinMarginedFuturesLimiter, 5), + cFuturesAccountInformationRate: request.GetRateLimiterWithWeight(coinMarginedFuturesLimiter, 5), + cFuturesKlineMaxRate: request.GetRateLimiterWithWeight(coinMarginedFuturesLimiter, 10), + cFuturesIndexMarkPriceRate: request.GetRateLimiterWithWeight(coinMarginedFuturesLimiter, 10), + cFuturesHistoricalTradesRate: request.GetRateLimiterWithWeight(coinMarginedFuturesLimiter, 20), + cFuturesCurrencyForceOrdersRate: request.GetRateLimiterWithWeight(coinMarginedFuturesLimiter, 20), + cFuturesTickerPriceHistoryRate: request.GetRateLimiterWithWeight(coinMarginedFuturesLimiter, 40), + cFuturesAllForceOrdersRate: request.GetRateLimiterWithWeight(coinMarginedFuturesOrdersLimiter, 50), + cFuturesOrdersDefaultRate: request.GetRateLimiterWithWeight(coinMarginedFuturesOrdersLimiter, 1), + cFuturesBatchOrdersRate: request.GetRateLimiterWithWeight(coinMarginedFuturesOrdersLimiter, 5), + cFuturesGetAllOpenOrdersRate: request.GetRateLimiterWithWeight(coinMarginedFuturesOrdersLimiter, 5), + cFuturesCancelAllOrdersRate: request.GetRateLimiterWithWeight(coinMarginedFuturesOrdersLimiter, 10), + cFuturesIncomeHistoryRate: request.GetRateLimiterWithWeight(coinMarginedFuturesOrdersLimiter, 20), + cFuturesSymbolOrdersRate: request.GetRateLimiterWithWeight(coinMarginedFuturesOrdersLimiter, 20), + cFuturesPairOrdersRate: request.GetRateLimiterWithWeight(coinMarginedFuturesOrdersLimiter, 40), + cFuturesOrderbook50Rate: request.GetRateLimiterWithWeight(coinMarginedFuturesOrdersLimiter, 2), + cFuturesOrderbook100Rate: request.GetRateLimiterWithWeight(coinMarginedFuturesOrdersLimiter, 5), + cFuturesOrderbook500Rate: request.GetRateLimiterWithWeight(coinMarginedFuturesOrdersLimiter, 10), + cFuturesOrderbook1000Rate: request.GetRateLimiterWithWeight(coinMarginedFuturesOrdersLimiter, 20), + uFuturesMultiAssetMarginRate: request.GetRateLimiterWithWeight(usdMarginedFuturesLimiter, 30), + uFuturesSetMultiAssetMarginRate: request.GetRateLimiterWithWeight(usdMarginedFuturesLimiter, 1), } - - if dl, ok := ctx.Deadline(); ok && dl.Before(time.Now().Add(finalDelay)) { - // Cancel all potential reservations to free up rate limiter if deadline - // is exceeded. - for x := range reserves { - reserves[x].Cancel() - } - return fmt.Errorf("rate limit delay of %s will exceed deadline: %w", - finalDelay, - context.DeadlineExceeded) - } - - time.Sleep(finalDelay) - return nil -} - -// SetRateLimit returns the rate limit for the exchange -func SetRateLimit() *RateLimit { - return &RateLimit{ - SpotRate: request.NewRateLimit(spotInterval, spotRequestRate), - SpotOrdersRate: request.NewRateLimit(spotOrderInterval, spotOrderRequestRate), - UFuturesRate: request.NewRateLimit(uFuturesInterval, uFuturesRequestRate), - UFuturesOrdersRate: request.NewRateLimit(uFuturesOrderInterval, uFuturesOrderRequestRate), - CFuturesRate: request.NewRateLimit(cFuturesInterval, cFuturesRequestRate), - CFuturesOrdersRate: request.NewRateLimit(cFuturesOrderInterval, cFuturesOrderRequestRate), - } -} - -func bestPriceLimit(symbol string) request.EndpointLimit { - if symbol == "" { - return spotOrderbookTickerAllRate - } - - return spotDefaultRate } func openOrdersLimit(symbol string) request.EndpointLimit { diff --git a/exchanges/binance/ratelimit_test.go b/exchanges/binance/ratelimit_test.go index 2b511966775..102d23c3801 100644 --- a/exchanges/binance/ratelimit_test.go +++ b/exchanges/binance/ratelimit_test.go @@ -3,9 +3,11 @@ package binance import ( "context" "errors" + "net/http" "testing" "time" + "github.com/stretchr/testify/require" "github.com/thrasher-corp/gocryptotrader/exchanges/request" ) @@ -18,19 +20,21 @@ func TestRateLimit_Limit(t *testing.T) { Limit request.EndpointLimit Deadline time.Time }{ - "All Orderbooks Ticker": {Expected: spotOrderbookTickerAllRate, Limit: bestPriceLimit("")}, - "Orderbook Ticker": {Expected: spotDefaultRate, Limit: bestPriceLimit(symbol)}, - "Open Orders": {Expected: spotOpenOrdersSpecificRate, Limit: openOrdersLimit(symbol)}, - "Orderbook Depth 5": {Expected: spotDefaultRate, Limit: orderbookLimit(5)}, - "Orderbook Depth 10": {Expected: spotDefaultRate, Limit: orderbookLimit(10)}, - "Orderbook Depth 20": {Expected: spotDefaultRate, Limit: orderbookLimit(20)}, - "Orderbook Depth 50": {Expected: spotDefaultRate, Limit: orderbookLimit(50)}, - "Orderbook Depth 100": {Expected: spotDefaultRate, Limit: orderbookLimit(100)}, - "Orderbook Depth 500": {Expected: spotOrderbookDepth500Rate, Limit: orderbookLimit(500)}, - "Orderbook Depth 1000": {Expected: spotOrderbookDepth1000Rate, Limit: orderbookLimit(1000)}, - "Orderbook Depth 5000": {Expected: spotOrderbookDepth5000Rate, Limit: orderbookLimit(5000)}, - "Exceeds deadline": {Expected: spotOrderbookDepth5000Rate, Limit: orderbookLimit(5000), Deadline: time.Now().Add(time.Nanosecond)}, + "Open Orders": {Expected: spotOpenOrdersSpecificRate, Limit: openOrdersLimit(symbol)}, + "Orderbook Depth 5": {Expected: spotDefaultRate, Limit: orderbookLimit(5)}, + "Orderbook Depth 10": {Expected: spotDefaultRate, Limit: orderbookLimit(10)}, + "Orderbook Depth 20": {Expected: spotDefaultRate, Limit: orderbookLimit(20)}, + "Orderbook Depth 50": {Expected: spotDefaultRate, Limit: orderbookLimit(50)}, + "Orderbook Depth 100": {Expected: spotDefaultRate, Limit: orderbookLimit(100)}, + "Orderbook Depth 500": {Expected: spotOrderbookDepth500Rate, Limit: orderbookLimit(500)}, + "Orderbook Depth 1000": {Expected: spotOrderbookDepth1000Rate, Limit: orderbookLimit(1000)}, + "Orderbook Depth 5000": {Expected: spotOrderbookDepth5000Rate, Limit: orderbookLimit(5000)}, + "Exceeds deadline": {Expected: spotOrderbookDepth5000Rate, Limit: orderbookLimit(5000), Deadline: time.Now().Add(time.Nanosecond)}, } + + rl, err := request.New("rateLimitTest", &http.Client{}, request.WithLimiter(GetRateLimits())) + require.NoError(t, err) + for name, tt := range testTable { tt := tt t.Run(name, func(t *testing.T) { @@ -48,8 +52,7 @@ func TestRateLimit_Limit(t *testing.T) { defer cancel() } - l := SetRateLimit() - if err := l.Limit(ctx, tt.Limit); err != nil && !errors.Is(err, context.DeadlineExceeded) { + if err := rl.InitiateRateLimit(ctx, tt.Limit); err != nil && !errors.Is(err, context.DeadlineExceeded) { t.Fatalf("error applying rate limit: %v", err) } }) @@ -64,13 +67,16 @@ func TestRateLimit_LimitStatic(t *testing.T) { "All Price Changes": spotPriceChangeAllRate, "All Orders": spotAllOrdersRate, } + + rl, err := request.New("rateLimitTest2", http.DefaultClient, request.WithLimiter(GetRateLimits())) + require.NoError(t, err) + for name, tt := range testTable { tt := tt t.Run(name, func(t *testing.T) { t.Parallel() - l := SetRateLimit() - if err := l.Limit(context.Background(), tt); err != nil { + if err := rl.InitiateRateLimit(context.Background(), tt); err != nil { t.Fatalf("error applying rate limit: %v", err) } }) diff --git a/exchanges/binance/type_convert.go b/exchanges/binance/type_convert.go index 69db96ef984..d03453f183c 100644 --- a/exchanges/binance/type_convert.go +++ b/exchanges/binance/type_convert.go @@ -325,28 +325,6 @@ func (a *WebsocketDepthStream) UnmarshalJSON(data []byte) error { return nil } -// UnmarshalJSON deserialises the JSON info, including the timestamp -func (a *wsAccountInfo) UnmarshalJSON(data []byte) error { - type Alias wsAccountInfo - aux := &struct { - Data struct { - EventTime binanceTime `json:"E"` - LastUpdated binanceTime `json:"u"` - *WsAccountInfoData - } `json:"data"` - *Alias - }{ - Alias: (*Alias)(a), - } - if err := json.Unmarshal(data, &aux); err != nil { - return err - } - a.Data = *aux.Data.WsAccountInfoData - a.Data.EventTime = aux.Data.EventTime.Time() - a.Data.LastUpdated = aux.Data.LastUpdated.Time() - return nil -} - // UnmarshalJSON deserialises the JSON info, including the timestamp func (a *wsAccountPosition) UnmarshalJSON(data []byte) error { type Alias wsAccountPosition diff --git a/exchanges/binanceus/binanceus_wrapper.go b/exchanges/binanceus/binanceus_wrapper.go index 3bb714271cd..e6ae5b74308 100644 --- a/exchanges/binanceus/binanceus_wrapper.go +++ b/exchanges/binanceus/binanceus_wrapper.go @@ -124,7 +124,7 @@ func (bi *Binanceus) SetDefaults() { } bi.Requester, err = request.New(bi.Name, common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout), - request.WithLimiter(SetRateLimit())) + request.WithLimiter(GetRateLimit())) if err != nil { log.Errorln(log.ExchangeSys, err) } diff --git a/exchanges/binanceus/ratelimit.go b/exchanges/binanceus/ratelimit.go index be9e510c3d4..34816a3a5c3 100644 --- a/exchanges/binanceus/ratelimit.go +++ b/exchanges/binanceus/ratelimit.go @@ -1,12 +1,9 @@ package binanceus import ( - "context" - "fmt" "time" "github.com/thrasher-corp/gocryptotrader/exchanges/request" - "golang.org/x/time/rate" ) const ( @@ -41,78 +38,30 @@ const ( spotAccountInformationRate ) -// RateLimit implements the request.Limiter interface -type RateLimit struct { - SpotRate *rate.Limiter - SpotOrdersRate *rate.Limiter -} - -// Limit executes rate limiting functionality for Binance -func (r *RateLimit) Limit(ctx context.Context, f request.EndpointLimit) error { - var limiter *rate.Limiter - var tokens int - switch f { - case spotDefaultRate: - limiter, tokens = r.SpotRate, 1 - case spotOrderbookTickerAllRate, - spotSymbolPriceAllRate: - limiter, tokens = r.SpotRate, 2 - case spotHistoricalTradesRate, - spotOrderbookDepth500Rate: - limiter, tokens = r.SpotRate, 5 - case spotOrderbookDepth1000Rate, - spotAccountInformationRate, - spotExchangeInfo, - spotTradesQueryRate: - limiter, tokens = r.SpotRate, 10 - case spotPriceChangeAllRate: - limiter, tokens = r.SpotRate, 40 - case spotOrderbookDepth5000Rate: - limiter, tokens = r.SpotRate, 50 - case spotOrderRate: - limiter, tokens = r.SpotOrdersRate, 1 - case spotOrderQueryRate, - spotSingleOCOOrderRate: - limiter, tokens = r.SpotOrdersRate, 2 - case spotOpenOrdersSpecificRate: - limiter, tokens = r.SpotOrdersRate, 3 - case spotAllOrdersRate, - spotAllOCOOrdersRate: - limiter, tokens = r.SpotOrdersRate, 10 - case spotOrderRateLimitRate: - limiter, tokens = r.SpotOrdersRate, 20 - case spotOpenOrdersAllRate: - limiter, tokens = r.SpotOrdersRate, 40 - default: - limiter, tokens = r.SpotRate, 1 - } - var finalDelay time.Duration - var reserves = make([]*rate.Reservation, tokens) - for i := 0; i < tokens; i++ { - // Consume tokens 1 at a time as this avoids needing burst capacity in the limiter, - // which would otherwise allow the rate limit to be exceeded over short periods - reserves[i] = limiter.Reserve() - finalDelay = reserves[i].Delay() - } - if dl, ok := ctx.Deadline(); ok && dl.Before(time.Now().Add(finalDelay)) { - // Cancel all potential reservations to free up rate limiter if deadline - // is exceeded. - for x := range reserves { - reserves[x].Cancel() - } - return fmt.Errorf("rate limit delay of %s will exceed deadline: %w", - finalDelay, - context.DeadlineExceeded) - } - time.Sleep(finalDelay) - return nil -} - -// SetRateLimit returns the rate limit for the exchange -func SetRateLimit() *RateLimit { - return &RateLimit{ - SpotRate: request.NewRateLimit(spotInterval, spotRequestRate), - SpotOrdersRate: request.NewRateLimit(spotOrderInterval, spotOrderRequestRate), +// GetRateLimit returns the rate limit for the exchange +func GetRateLimit() request.RateLimitDefinitions { + spotRate := request.NewRateLimit(spotInterval, spotRequestRate) + spotOrdersRate := request.NewRateLimit(spotOrderInterval, spotOrderRequestRate) + return request.RateLimitDefinitions{ + spotDefaultRate: request.GetRateLimiterWithWeight(spotRate, 1), + spotOrderbookTickerAllRate: request.GetRateLimiterWithWeight(spotRate, 2), + spotSymbolPriceAllRate: request.GetRateLimiterWithWeight(spotRate, 2), + spotHistoricalTradesRate: request.GetRateLimiterWithWeight(spotRate, 5), + spotOrderbookDepth500Rate: request.GetRateLimiterWithWeight(spotRate, 5), + spotOrderbookDepth1000Rate: request.GetRateLimiterWithWeight(spotRate, 10), + spotAccountInformationRate: request.GetRateLimiterWithWeight(spotRate, 10), + spotExchangeInfo: request.GetRateLimiterWithWeight(spotRate, 10), + spotTradesQueryRate: request.GetRateLimiterWithWeight(spotRate, 10), + spotPriceChangeAllRate: request.GetRateLimiterWithWeight(spotRate, 40), + spotOrderbookDepth5000Rate: request.GetRateLimiterWithWeight(spotRate, 50), + spotOrderRate: request.GetRateLimiterWithWeight(spotOrdersRate, 1), + spotOrderQueryRate: request.GetRateLimiterWithWeight(spotOrdersRate, 2), + spotSingleOCOOrderRate: request.GetRateLimiterWithWeight(spotOrdersRate, 2), + spotOpenOrdersSpecificRate: request.GetRateLimiterWithWeight(spotOrdersRate, 3), + spotAllOrdersRate: request.GetRateLimiterWithWeight(spotOrdersRate, 10), + spotAllOCOOrdersRate: request.GetRateLimiterWithWeight(spotOrdersRate, 10), + spotOrderRateLimitRate: request.GetRateLimiterWithWeight(spotOrdersRate, 20), + spotOpenOrdersAllRate: request.GetRateLimiterWithWeight(spotOrdersRate, 40), } } diff --git a/exchanges/bitfinex/bitfinex_wrapper.go b/exchanges/bitfinex/bitfinex_wrapper.go index 86384d011e7..41e21140c3b 100644 --- a/exchanges/bitfinex/bitfinex_wrapper.go +++ b/exchanges/bitfinex/bitfinex_wrapper.go @@ -163,7 +163,7 @@ func (b *Bitfinex) SetDefaults() { b.Requester, err = request.New(b.Name, common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout), - request.WithLimiter(SetRateLimit())) + request.WithLimiter(GetRateLimit())) if err != nil { log.Errorln(log.ExchangeSys, err) } diff --git a/exchanges/bitfinex/ratelimit.go b/exchanges/bitfinex/ratelimit.go index ff30f9d868e..8c6b08762eb 100644 --- a/exchanges/bitfinex/ratelimit.go +++ b/exchanges/bitfinex/ratelimit.go @@ -1,12 +1,9 @@ package bitfinex import ( - "context" - "errors" "time" "github.com/thrasher-corp/gocryptotrader/exchanges/request" - "golang.org/x/time/rate" ) const ( @@ -175,316 +172,84 @@ const ( lends ) -// RateLimit implements the rate.Limiter interface -type RateLimit struct { - PlatformStatus *rate.Limiter - TickerBatch *rate.Limiter - Ticker *rate.Limiter - Trade *rate.Limiter - Orderbook *rate.Limiter - Stats *rate.Limiter - Candle *rate.Limiter - Configs *rate.Limiter - Status *rate.Limiter - Liquid *rate.Limiter - LeaderBoard *rate.Limiter - MarketAveragePrice *rate.Limiter - Fx *rate.Limiter - AccountWalletBalance *rate.Limiter - AccountWalletHistory *rate.Limiter - // Orders - - RetrieveOrder *rate.Limiter - SubmitOrder *rate.Limiter - UpdateOrder *rate.Limiter - CancelOrder *rate.Limiter - OrderBatch *rate.Limiter - CancelBatch *rate.Limiter - OrderHistory *rate.Limiter - GetOrderTrades *rate.Limiter - GetTrades *rate.Limiter - GetLedgers *rate.Limiter - // Positions - - GetAccountMarginInfo *rate.Limiter - GetActivePositions *rate.Limiter - ClaimPosition *rate.Limiter - GetPositionHistory *rate.Limiter - GetPositionAudit *rate.Limiter - UpdateCollateralOnPosition *rate.Limiter - // Margin funding - - GetActiveFundingOffers *rate.Limiter - SubmitFundingOffer *rate.Limiter - CancelFundingOffer *rate.Limiter - CancelAllFundingOffer *rate.Limiter - CloseFunding *rate.Limiter - FundingAutoRenew *rate.Limiter - KeepFunding *rate.Limiter - GetOffersHistory *rate.Limiter - GetFundingLoans *rate.Limiter - GetFundingLoanHistory *rate.Limiter - GetFundingCredits *rate.Limiter - GetFundingCreditsHistory *rate.Limiter - GetFundingTrades *rate.Limiter - GetFundingInfo *rate.Limiter - // Account actions - GetUserInfo *rate.Limiter - TransferBetweenWallets *rate.Limiter - GetDepositAddress *rate.Limiter - Withdrawal *rate.Limiter - GetMovements *rate.Limiter - GetAlertList *rate.Limiter - SetPriceAlert *rate.Limiter - DeletePriceAlert *rate.Limiter - GetBalanceForOrdersOffers *rate.Limiter - UserSettingsWrite *rate.Limiter - UserSettingsRead *rate.Limiter - UserSettingsDelete *rate.Limiter - // Account V1 endpoints - GetAccountFees *rate.Limiter - GetWithdrawalFees *rate.Limiter - GetAccountSummary *rate.Limiter - NewDepositAddress *rate.Limiter - GetKeyPermissions *rate.Limiter - GetMarginInfo *rate.Limiter - GetAccountBalance *rate.Limiter - WalletTransfer *rate.Limiter - WithdrawV1 *rate.Limiter - OrderV1 *rate.Limiter - OrderMulti *rate.Limiter - StatsV1 *rate.Limiter - Fundingbook *rate.Limiter - Lends *rate.Limiter -} - -// Limit limits outbound requests -func (r *RateLimit) Limit(ctx context.Context, f request.EndpointLimit) error { - switch f { - case platformStatus: - return r.PlatformStatus.Wait(ctx) - case tickerBatch: - return r.TickerBatch.Wait(ctx) - case tickerFunction: - return r.Ticker.Wait(ctx) - case tradeRateLimit: - return r.Trade.Wait(ctx) - case orderbookFunction: - return r.Orderbook.Wait(ctx) - case stats: - return r.Stats.Wait(ctx) - case candle: - return r.Candle.Wait(ctx) - case configs: - return r.Configs.Wait(ctx) - case status: - return r.Stats.Wait(ctx) - case liquid: - return r.Liquid.Wait(ctx) - case leaderBoard: - return r.LeaderBoard.Wait(ctx) - case marketAveragePrice: - return r.MarketAveragePrice.Wait(ctx) - case fx: - return r.Fx.Wait(ctx) - case accountWalletBalance: - return r.AccountWalletBalance.Wait(ctx) - case accountWalletHistory: - return r.AccountWalletHistory.Wait(ctx) - case retrieveOrder: - return r.RetrieveOrder.Wait(ctx) - case submitOrder: - return r.SubmitOrder.Wait(ctx) - case updateOrder: - return r.UpdateOrder.Wait(ctx) - case cancelOrder: - return r.CancelOrder.Wait(ctx) - case orderBatch: - return r.OrderBatch.Wait(ctx) - case cancelBatch: - return r.CancelBatch.Wait(ctx) - case orderHistory: - return r.OrderHistory.Wait(ctx) - case getOrderTrades: - return r.GetOrderTrades.Wait(ctx) - case getTrades: - return r.GetTrades.Wait(ctx) - case getLedgers: - return r.GetLedgers.Wait(ctx) - case getAccountMarginInfo: - return r.GetAccountMarginInfo.Wait(ctx) - case getActivePositions: - return r.GetActivePositions.Wait(ctx) - case claimPosition: - return r.ClaimPosition.Wait(ctx) - case getPositionHistory: - return r.GetPositionHistory.Wait(ctx) - case getPositionAudit: - return r.GetPositionAudit.Wait(ctx) - case updateCollateralOnPosition: - return r.UpdateCollateralOnPosition.Wait(ctx) - case getActiveFundingOffers: - return r.GetActiveFundingOffers.Wait(ctx) - case submitFundingOffer: - return r.SubmitFundingOffer.Wait(ctx) - case cancelFundingOffer: - return r.CancelFundingOffer.Wait(ctx) - case cancelAllFundingOffer: - return r.CancelAllFundingOffer.Wait(ctx) - case closeFunding: - return r.CloseFunding.Wait(ctx) - case fundingAutoRenew: - return r.FundingAutoRenew.Wait(ctx) - case keepFunding: - return r.KeepFunding.Wait(ctx) - case getOffersHistory: - return r.GetOffersHistory.Wait(ctx) - case getFundingLoans: - return r.GetFundingLoans.Wait(ctx) - case getFundingLoanHistory: - return r.GetFundingLoanHistory.Wait(ctx) - case getFundingCredits: - return r.GetFundingCredits.Wait(ctx) - case getFundingCreditsHistory: - return r.GetFundingCreditsHistory.Wait(ctx) - case getFundingTrades: - return r.GetFundingTrades.Wait(ctx) - case getFundingInfo: - return r.GetFundingInfo.Wait(ctx) - case getUserInfo: - return r.GetUserInfo.Wait(ctx) - case transferBetweenWallets: - return r.TransferBetweenWallets.Wait(ctx) - case getDepositAddress: - return r.GetDepositAddress.Wait(ctx) - case withdrawal: - return r.Withdrawal.Wait(ctx) - case getMovements: - return r.GetMovements.Wait(ctx) - case getAlertList: - return r.GetAlertList.Wait(ctx) - case setPriceAlert: - return r.SetPriceAlert.Wait(ctx) - case deletePriceAlert: - return r.DeletePriceAlert.Wait(ctx) - case getBalanceForOrdersOffers: - return r.GetBalanceForOrdersOffers.Wait(ctx) - case userSettingsWrite: - return r.UserSettingsWrite.Wait(ctx) - case userSettingsRead: - return r.UserSettingsRead.Wait(ctx) - case userSettingsDelete: - return r.UserSettingsDelete.Wait(ctx) - - // Bitfinex V1 API - case getAccountFees: - return r.GetAccountFees.Wait(ctx) - case getWithdrawalFees: - return r.GetWithdrawalFees.Wait(ctx) - case getAccountSummary: - return r.GetAccountSummary.Wait(ctx) - case newDepositAddress: - return r.NewDepositAddress.Wait(ctx) - case getKeyPermissions: - return r.GetKeyPermissions.Wait(ctx) - case getMarginInfo: - return r.GetMarginInfo.Wait(ctx) - case getAccountBalance: - return r.GetAccountBalance.Wait(ctx) - case walletTransfer: - return r.WalletTransfer.Wait(ctx) - case withdrawV1: - return r.WithdrawV1.Wait(ctx) - case orderV1: - return r.OrderV1.Wait(ctx) - case orderMulti: - return r.OrderMulti.Wait(ctx) - case statsV1: - return r.Stats.Wait(ctx) - case fundingbook: - return r.Fundingbook.Wait(ctx) - case lends: - return r.Lends.Wait(ctx) - default: - return errors.New("endpoint rate limit functionality not found") - } -} - -// SetRateLimit returns the rate limit for the exchange -func SetRateLimit() *RateLimit { - return &RateLimit{ - PlatformStatus: request.NewRateLimit(requestLimitInterval, platformStatusReqRate), - TickerBatch: request.NewRateLimit(requestLimitInterval, tickerBatchReqRate), - Ticker: request.NewRateLimit(requestLimitInterval, tickerReqRate), - Trade: request.NewRateLimit(requestLimitInterval, tradeReqRate), - Orderbook: request.NewRateLimit(requestLimitInterval, orderbookReqRate), - Stats: request.NewRateLimit(requestLimitInterval, statsReqRate), - Candle: request.NewRateLimit(requestLimitInterval, candleReqRate), - Configs: request.NewRateLimit(requestLimitInterval, configsReqRate), - Status: request.NewRateLimit(requestLimitInterval, statusReqRate), - Liquid: request.NewRateLimit(requestLimitInterval, liquidReqRate), - LeaderBoard: request.NewRateLimit(requestLimitInterval, leaderBoardReqRate), - MarketAveragePrice: request.NewRateLimit(requestLimitInterval, marketAveragePriceReqRate), - Fx: request.NewRateLimit(requestLimitInterval, fxReqRate), - AccountWalletBalance: request.NewRateLimit(requestLimitInterval, accountWalletBalanceReqRate), - AccountWalletHistory: request.NewRateLimit(requestLimitInterval, accountWalletHistoryReqRate), +// GetRateLimit returns the rate limit for the exchange +func GetRateLimit() request.RateLimitDefinitions { + return request.RateLimitDefinitions{ + platformStatus: request.NewRateLimitWithWeight(requestLimitInterval, platformStatusReqRate, 1), + tickerBatch: request.NewRateLimitWithWeight(requestLimitInterval, tickerBatchReqRate, 1), + tickerFunction: request.NewRateLimitWithWeight(requestLimitInterval, tickerReqRate, 1), + tradeRateLimit: request.NewRateLimitWithWeight(requestLimitInterval, tradeReqRate, 1), + orderbookFunction: request.NewRateLimitWithWeight(requestLimitInterval, orderbookReqRate, 1), + stats: request.NewRateLimitWithWeight(requestLimitInterval, statsReqRate, 1), + candle: request.NewRateLimitWithWeight(requestLimitInterval, candleReqRate, 1), + configs: request.NewRateLimitWithWeight(requestLimitInterval, configsReqRate, 1), + status: request.NewRateLimitWithWeight(requestLimitInterval, statusReqRate, 1), + liquid: request.NewRateLimitWithWeight(requestLimitInterval, liquidReqRate, 1), + leaderBoard: request.NewRateLimitWithWeight(requestLimitInterval, leaderBoardReqRate, 1), + marketAveragePrice: request.NewRateLimitWithWeight(requestLimitInterval, marketAveragePriceReqRate, 1), + fx: request.NewRateLimitWithWeight(requestLimitInterval, fxReqRate, 1), + accountWalletBalance: request.NewRateLimitWithWeight(requestLimitInterval, accountWalletBalanceReqRate, 1), + accountWalletHistory: request.NewRateLimitWithWeight(requestLimitInterval, accountWalletHistoryReqRate, 1), // Orders - - RetrieveOrder: request.NewRateLimit(requestLimitInterval, retrieveOrderReqRate), - SubmitOrder: request.NewRateLimit(requestLimitInterval, submitOrderReqRate), - UpdateOrder: request.NewRateLimit(requestLimitInterval, updateOrderReqRate), - CancelOrder: request.NewRateLimit(requestLimitInterval, cancelOrderReqRate), - OrderBatch: request.NewRateLimit(requestLimitInterval, orderBatchReqRate), - CancelBatch: request.NewRateLimit(requestLimitInterval, cancelBatchReqRate), - OrderHistory: request.NewRateLimit(requestLimitInterval, orderHistoryReqRate), - GetOrderTrades: request.NewRateLimit(requestLimitInterval, getOrderTradesReqRate), - GetTrades: request.NewRateLimit(requestLimitInterval, getTradesReqRate), - GetLedgers: request.NewRateLimit(requestLimitInterval, getLedgersReqRate), + retrieveOrder: request.NewRateLimitWithWeight(requestLimitInterval, retrieveOrderReqRate, 1), + submitOrder: request.NewRateLimitWithWeight(requestLimitInterval, submitOrderReqRate, 1), + updateOrder: request.NewRateLimitWithWeight(requestLimitInterval, updateOrderReqRate, 1), + cancelOrder: request.NewRateLimitWithWeight(requestLimitInterval, cancelOrderReqRate, 1), + orderBatch: request.NewRateLimitWithWeight(requestLimitInterval, orderBatchReqRate, 1), + cancelBatch: request.NewRateLimitWithWeight(requestLimitInterval, cancelBatchReqRate, 1), + orderHistory: request.NewRateLimitWithWeight(requestLimitInterval, orderHistoryReqRate, 1), + getOrderTrades: request.NewRateLimitWithWeight(requestLimitInterval, getOrderTradesReqRate, 1), + getTrades: request.NewRateLimitWithWeight(requestLimitInterval, getTradesReqRate, 1), + getLedgers: request.NewRateLimitWithWeight(requestLimitInterval, getLedgersReqRate, 1), // Positions - - GetAccountMarginInfo: request.NewRateLimit(requestLimitInterval, getAccountMarginInfoReqRate), - GetActivePositions: request.NewRateLimit(requestLimitInterval, getActivePositionsReqRate), - ClaimPosition: request.NewRateLimit(requestLimitInterval, claimPositionReqRate), - GetPositionHistory: request.NewRateLimit(requestLimitInterval, getPositionAuditReqRate), - GetPositionAudit: request.NewRateLimit(requestLimitInterval, getPositionAuditReqRate), - UpdateCollateralOnPosition: request.NewRateLimit(requestLimitInterval, updateCollateralOnPositionReqRate), + getAccountMarginInfo: request.NewRateLimitWithWeight(requestLimitInterval, getAccountMarginInfoReqRate, 1), + getActivePositions: request.NewRateLimitWithWeight(requestLimitInterval, getActivePositionsReqRate, 1), + claimPosition: request.NewRateLimitWithWeight(requestLimitInterval, claimPositionReqRate, 1), + getPositionHistory: request.NewRateLimitWithWeight(requestLimitInterval, getPositionAuditReqRate, 1), + getPositionAudit: request.NewRateLimitWithWeight(requestLimitInterval, getPositionAuditReqRate, 1), + updateCollateralOnPosition: request.NewRateLimitWithWeight(requestLimitInterval, updateCollateralOnPositionReqRate, 1), // Margin funding - - GetActiveFundingOffers: request.NewRateLimit(requestLimitInterval, getActiveFundingOffersReqRate), - SubmitFundingOffer: request.NewRateLimit(requestLimitInterval, submitFundingOfferReqRate), - CancelFundingOffer: request.NewRateLimit(requestLimitInterval, cancelFundingOfferReqRate), - CancelAllFundingOffer: request.NewRateLimit(requestLimitInterval, cancelAllFundingOfferReqRate), - CloseFunding: request.NewRateLimit(requestLimitInterval, closeFundingReqRate), - FundingAutoRenew: request.NewRateLimit(requestLimitInterval, fundingAutoRenewReqRate), - KeepFunding: request.NewRateLimit(requestLimitInterval, keepFundingReqRate), - GetOffersHistory: request.NewRateLimit(requestLimitInterval, getOffersHistoryReqRate), - GetFundingLoans: request.NewRateLimit(requestLimitInterval, getOffersHistoryReqRate), - GetFundingLoanHistory: request.NewRateLimit(requestLimitInterval, getFundingLoanHistoryReqRate), - GetFundingCredits: request.NewRateLimit(requestLimitInterval, getFundingCreditsReqRate), - GetFundingCreditsHistory: request.NewRateLimit(requestLimitInterval, getFundingCreditsHistoryReqRate), - GetFundingTrades: request.NewRateLimit(requestLimitInterval, getFundingTradesReqRate), - GetFundingInfo: request.NewRateLimit(requestLimitInterval, getFundingInfoReqRate), + getActiveFundingOffers: request.NewRateLimitWithWeight(requestLimitInterval, getActiveFundingOffersReqRate, 1), + submitFundingOffer: request.NewRateLimitWithWeight(requestLimitInterval, submitFundingOfferReqRate, 1), + cancelFundingOffer: request.NewRateLimitWithWeight(requestLimitInterval, cancelFundingOfferReqRate, 1), + cancelAllFundingOffer: request.NewRateLimitWithWeight(requestLimitInterval, cancelAllFundingOfferReqRate, 1), + closeFunding: request.NewRateLimitWithWeight(requestLimitInterval, closeFundingReqRate, 1), + fundingAutoRenew: request.NewRateLimitWithWeight(requestLimitInterval, fundingAutoRenewReqRate, 1), + keepFunding: request.NewRateLimitWithWeight(requestLimitInterval, keepFundingReqRate, 1), + getOffersHistory: request.NewRateLimitWithWeight(requestLimitInterval, getOffersHistoryReqRate, 1), + getFundingLoans: request.NewRateLimitWithWeight(requestLimitInterval, getOffersHistoryReqRate, 1), + getFundingLoanHistory: request.NewRateLimitWithWeight(requestLimitInterval, getFundingLoanHistoryReqRate, 1), + getFundingCredits: request.NewRateLimitWithWeight(requestLimitInterval, getFundingCreditsReqRate, 1), + getFundingCreditsHistory: request.NewRateLimitWithWeight(requestLimitInterval, getFundingCreditsHistoryReqRate, 1), + getFundingTrades: request.NewRateLimitWithWeight(requestLimitInterval, getFundingTradesReqRate, 1), + getFundingInfo: request.NewRateLimitWithWeight(requestLimitInterval, getFundingInfoReqRate, 1), // Account actions - GetUserInfo: request.NewRateLimit(requestLimitInterval, getUserInfoReqRate), - TransferBetweenWallets: request.NewRateLimit(requestLimitInterval, transferBetweenWalletsReqRate), - GetDepositAddress: request.NewRateLimit(requestLimitInterval, getDepositAddressReqRate), - Withdrawal: request.NewRateLimit(requestLimitInterval, withdrawalReqRate), - GetMovements: request.NewRateLimit(requestLimitInterval, getMovementsReqRate), - GetAlertList: request.NewRateLimit(requestLimitInterval, getAlertListReqRate), - SetPriceAlert: request.NewRateLimit(requestLimitInterval, setPriceAlertReqRate), - DeletePriceAlert: request.NewRateLimit(requestLimitInterval, deletePriceAlertReqRate), - GetBalanceForOrdersOffers: request.NewRateLimit(requestLimitInterval, getBalanceForOrdersOffersReqRate), - UserSettingsWrite: request.NewRateLimit(requestLimitInterval, userSettingsWriteReqRate), - UserSettingsRead: request.NewRateLimit(requestLimitInterval, userSettingsReadReqRate), - UserSettingsDelete: request.NewRateLimit(requestLimitInterval, userSettingsDeleteReqRate), + getUserInfo: request.NewRateLimitWithWeight(requestLimitInterval, getUserInfoReqRate, 1), + transferBetweenWallets: request.NewRateLimitWithWeight(requestLimitInterval, transferBetweenWalletsReqRate, 1), + getDepositAddress: request.NewRateLimitWithWeight(requestLimitInterval, getDepositAddressReqRate, 1), + withdrawal: request.NewRateLimitWithWeight(requestLimitInterval, withdrawalReqRate, 1), + getMovements: request.NewRateLimitWithWeight(requestLimitInterval, getMovementsReqRate, 1), + getAlertList: request.NewRateLimitWithWeight(requestLimitInterval, getAlertListReqRate, 1), + setPriceAlert: request.NewRateLimitWithWeight(requestLimitInterval, setPriceAlertReqRate, 1), + deletePriceAlert: request.NewRateLimitWithWeight(requestLimitInterval, deletePriceAlertReqRate, 1), + getBalanceForOrdersOffers: request.NewRateLimitWithWeight(requestLimitInterval, getBalanceForOrdersOffersReqRate, 1), + userSettingsWrite: request.NewRateLimitWithWeight(requestLimitInterval, userSettingsWriteReqRate, 1), + userSettingsRead: request.NewRateLimitWithWeight(requestLimitInterval, userSettingsReadReqRate, 1), + userSettingsDelete: request.NewRateLimitWithWeight(requestLimitInterval, userSettingsDeleteReqRate, 1), // Account V1 endpoints - GetAccountFees: request.NewRateLimit(requestLimitInterval, getAccountFeesReqRate), - GetWithdrawalFees: request.NewRateLimit(requestLimitInterval, getWithdrawalFeesReqRate), - GetAccountSummary: request.NewRateLimit(requestLimitInterval, getAccountSummaryReqRate), - NewDepositAddress: request.NewRateLimit(requestLimitInterval, newDepositAddressReqRate), - GetKeyPermissions: request.NewRateLimit(requestLimitInterval, getKeyPermissionsReqRate), - GetMarginInfo: request.NewRateLimit(requestLimitInterval, getMarginInfoReqRate), - GetAccountBalance: request.NewRateLimit(requestLimitInterval, getAccountBalanceReqRate), - WalletTransfer: request.NewRateLimit(requestLimitInterval, walletTransferReqRate), - WithdrawV1: request.NewRateLimit(requestLimitInterval, withdrawV1ReqRate), - OrderV1: request.NewRateLimit(requestLimitInterval, orderV1ReqRate), - OrderMulti: request.NewRateLimit(requestLimitInterval, orderMultiReqRate), - StatsV1: request.NewRateLimit(requestLimitInterval, statsV1ReqRate), - Fundingbook: request.NewRateLimit(requestLimitInterval, fundingbookReqRate), - Lends: request.NewRateLimit(requestLimitInterval, lendsReqRate), + getAccountFees: request.NewRateLimitWithWeight(requestLimitInterval, getAccountFeesReqRate, 1), + getWithdrawalFees: request.NewRateLimitWithWeight(requestLimitInterval, getWithdrawalFeesReqRate, 1), + getAccountSummary: request.NewRateLimitWithWeight(requestLimitInterval, getAccountSummaryReqRate, 1), + newDepositAddress: request.NewRateLimitWithWeight(requestLimitInterval, newDepositAddressReqRate, 1), + getKeyPermissions: request.NewRateLimitWithWeight(requestLimitInterval, getKeyPermissionsReqRate, 1), + getMarginInfo: request.NewRateLimitWithWeight(requestLimitInterval, getMarginInfoReqRate, 1), + getAccountBalance: request.NewRateLimitWithWeight(requestLimitInterval, getAccountBalanceReqRate, 1), + walletTransfer: request.NewRateLimitWithWeight(requestLimitInterval, walletTransferReqRate, 1), + withdrawV1: request.NewRateLimitWithWeight(requestLimitInterval, withdrawV1ReqRate, 1), + orderV1: request.NewRateLimitWithWeight(requestLimitInterval, orderV1ReqRate, 1), + orderMulti: request.NewRateLimitWithWeight(requestLimitInterval, orderMultiReqRate, 1), + statsV1: request.NewRateLimitWithWeight(requestLimitInterval, statsV1ReqRate, 1), + fundingbook: request.NewRateLimitWithWeight(requestLimitInterval, fundingbookReqRate, 1), + lends: request.NewRateLimitWithWeight(requestLimitInterval, lendsReqRate, 1), } } diff --git a/exchanges/bitflyer/bitflyer.go b/exchanges/bitflyer/bitflyer.go index d7e96947842..6ad8ae5aa5a 100644 --- a/exchanges/bitflyer/bitflyer.go +++ b/exchanges/bitflyer/bitflyer.go @@ -300,7 +300,7 @@ func (b *Bitflyer) SendHTTPRequest(ctx context.Context, ep exchange.URL, path st HTTPDebugging: b.HTTPDebugging, HTTPRecording: b.HTTPRecording, } - return b.SendPayload(ctx, request.Unset, func() (*request.Item, error) { + return b.SendPayload(ctx, request.UnAuth, func() (*request.Item, error) { return item, nil }, request.UnauthenticatedRequest) } diff --git a/exchanges/bitflyer/bitflyer_wrapper.go b/exchanges/bitflyer/bitflyer_wrapper.go index 150ec88e7c7..767c107be02 100644 --- a/exchanges/bitflyer/bitflyer_wrapper.go +++ b/exchanges/bitflyer/bitflyer_wrapper.go @@ -74,7 +74,7 @@ func (b *Bitflyer) SetDefaults() { b.Requester, err = request.New(b.Name, common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout), - request.WithLimiter(SetRateLimit())) + request.WithLimiter(GetRateLimit())) if err != nil { log.Errorln(log.ExchangeSys, err) } diff --git a/exchanges/bitflyer/ratelimit.go b/exchanges/bitflyer/ratelimit.go index 564b5f34cd1..9e4ab01b63a 100644 --- a/exchanges/bitflyer/ratelimit.go +++ b/exchanges/bitflyer/ratelimit.go @@ -1,11 +1,9 @@ package bitflyer import ( - "context" "time" "github.com/thrasher-corp/gocryptotrader/exchanges/request" - "golang.org/x/time/rate" ) // Exchange specific rate limit consts @@ -17,50 +15,14 @@ const ( bitflyerPublicRequestRate = 500 ) -// RateLimit implements the rate.Limiter interface -type RateLimit struct { - Auth *rate.Limiter - UnAuth *rate.Limiter - - // Send a New Order - // Submit New Parent Order (Special order) - // Cancel All Orders - Order *rate.Limiter - LowVolume *rate.Limiter -} - -// Limit limits outbound requests -func (r *RateLimit) Limit(ctx context.Context, f request.EndpointLimit) error { - switch f { - case request.Auth: - return r.Auth.Wait(ctx) - case orders: - err := r.Auth.Wait(ctx) - if err != nil { - return err - } - return r.Order.Wait(ctx) - case lowVolume: - err := r.LowVolume.Wait(ctx) - if err != nil { - return err - } - err = r.Order.Wait(ctx) - if err != nil { - return err - } - return r.Auth.Wait(ctx) - default: - return r.UnAuth.Wait(ctx) - } -} - -// SetRateLimit returns the rate limit for the exchange -func SetRateLimit() *RateLimit { - return &RateLimit{ - Auth: request.NewRateLimit(biflyerRateInterval, bitflyerPrivateRequestRate), - UnAuth: request.NewRateLimit(biflyerRateInterval, bitflyerPublicRequestRate), - Order: request.NewRateLimit(biflyerRateInterval, bitflyerPrivateSendOrderRequestRate), - LowVolume: request.NewRateLimit(time.Minute, bitflyerPrivateLowVolumeRequestRate), +// GetRateLimit returns the rate limit for the exchange +func GetRateLimit() request.RateLimitDefinitions { + return request.RateLimitDefinitions{ + request.Auth: request.NewRateLimitWithWeight(biflyerRateInterval, bitflyerPrivateRequestRate, 1), + request.UnAuth: request.NewRateLimitWithWeight(biflyerRateInterval, bitflyerPublicRequestRate, 1), + // TODO: Below limits need to also take from auth rate limit. This + // can not yet be tested and verified so is left not done for now. + orders: request.NewRateLimitWithWeight(biflyerRateInterval, bitflyerPrivateSendOrderRequestRate, 1), + lowVolume: request.NewRateLimitWithWeight(time.Minute, bitflyerPrivateLowVolumeRequestRate, 1), } } diff --git a/exchanges/bithumb/bithumb_wrapper.go b/exchanges/bithumb/bithumb_wrapper.go index 9c528ce268f..4b0dec3325b 100644 --- a/exchanges/bithumb/bithumb_wrapper.go +++ b/exchanges/bithumb/bithumb_wrapper.go @@ -114,7 +114,7 @@ func (b *Bithumb) SetDefaults() { } b.Requester, err = request.New(b.Name, common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout), - request.WithLimiter(SetRateLimit())) + request.WithLimiter(GetRateLimit())) if err != nil { log.Errorln(log.ExchangeSys, err) } diff --git a/exchanges/bithumb/ratelimit.go b/exchanges/bithumb/ratelimit.go index fd4316bb86a..9ef74e17db2 100644 --- a/exchanges/bithumb/ratelimit.go +++ b/exchanges/bithumb/ratelimit.go @@ -1,11 +1,9 @@ package bithumb import ( - "context" "time" "github.com/thrasher-corp/gocryptotrader/exchanges/request" - "golang.org/x/time/rate" ) // Exchange specific rate limit consts @@ -15,24 +13,10 @@ const ( bithumbUnauthRate = 95 ) -// RateLimit implements the request.Limiter interface -type RateLimit struct { - Auth *rate.Limiter - UnAuth *rate.Limiter -} - -// Limit limits requests -func (r *RateLimit) Limit(ctx context.Context, f request.EndpointLimit) error { - if f == request.Auth { - return r.Auth.Wait(ctx) - } - return r.UnAuth.Wait(ctx) -} - -// SetRateLimit returns the rate limit for the exchange -func SetRateLimit() *RateLimit { - return &RateLimit{ - Auth: request.NewRateLimit(bithumbRateInterval, bithumbAuthRate), - UnAuth: request.NewRateLimit(bithumbRateInterval, bithumbUnauthRate), +// GetRateLimit returns the rate limit for the exchange +func GetRateLimit() request.RateLimitDefinitions { + return request.RateLimitDefinitions{ + request.Auth: request.NewRateLimitWithWeight(bithumbRateInterval, bithumbAuthRate, 1), + request.Unset: request.NewRateLimitWithWeight(bithumbRateInterval, bithumbUnauthRate, 1), } } diff --git a/exchanges/bitmex/bitmex.go b/exchanges/bitmex/bitmex.go index 2a065ca7229..73bd5637d82 100644 --- a/exchanges/bitmex/bitmex.go +++ b/exchanges/bitmex/bitmex.go @@ -860,7 +860,7 @@ func (b *Bitmex) SendHTTPRequest(ctx context.Context, ep exchange.URL, path stri HTTPRecording: b.HTTPRecording, } - err = b.SendPayload(ctx, request.Unset, func() (*request.Item, error) { + err = b.SendPayload(ctx, request.UnAuth, func() (*request.Item, error) { return item, nil }, request.UnauthenticatedRequest) if err != nil { diff --git a/exchanges/bitmex/bitmex_wrapper.go b/exchanges/bitmex/bitmex_wrapper.go index b9017625f1a..b5f53e03eb2 100644 --- a/exchanges/bitmex/bitmex_wrapper.go +++ b/exchanges/bitmex/bitmex_wrapper.go @@ -140,7 +140,7 @@ func (b *Bitmex) SetDefaults() { b.Requester, err = request.New(b.Name, common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout), - request.WithLimiter(SetRateLimit())) + request.WithLimiter(GetRateLimit())) if err != nil { log.Errorln(log.ExchangeSys, err) } diff --git a/exchanges/bitmex/ratelimit.go b/exchanges/bitmex/ratelimit.go index 5207d2894a2..c226d50fe4e 100644 --- a/exchanges/bitmex/ratelimit.go +++ b/exchanges/bitmex/ratelimit.go @@ -1,11 +1,9 @@ package bitmex import ( - "context" "time" "github.com/thrasher-corp/gocryptotrader/exchanges/request" - "golang.org/x/time/rate" ) // Bitmex rate limits @@ -15,24 +13,10 @@ const ( bitmexAuthRate = 60 ) -// RateLimit implements the request.Limiter interface -type RateLimit struct { - Auth *rate.Limiter - UnAuth *rate.Limiter -} - -// Limit limits outbound calls -func (r *RateLimit) Limit(ctx context.Context, f request.EndpointLimit) error { - if f == request.Auth { - return r.Auth.Wait(ctx) - } - return r.UnAuth.Wait(ctx) -} - -// SetRateLimit returns the rate limit for the exchange -func SetRateLimit() *RateLimit { - return &RateLimit{ - Auth: request.NewRateLimit(bitmexRateInterval, bitmexAuthRate), - UnAuth: request.NewRateLimit(bitmexRateInterval, bitmexUnauthRate), +// GetRateLimit returns the rate limit for the exchange +func GetRateLimit() request.RateLimitDefinitions { + return request.RateLimitDefinitions{ + request.Auth: request.NewRateLimitWithWeight(bitmexRateInterval, bitmexAuthRate, 1), + request.UnAuth: request.NewRateLimitWithWeight(bitmexRateInterval, bitmexUnauthRate, 1), } } diff --git a/exchanges/bitstamp/bitstamp_wrapper.go b/exchanges/bitstamp/bitstamp_wrapper.go index d9c3071bea0..2ea7f3dc3db 100644 --- a/exchanges/bitstamp/bitstamp_wrapper.go +++ b/exchanges/bitstamp/bitstamp_wrapper.go @@ -111,7 +111,7 @@ func (b *Bitstamp) SetDefaults() { b.Requester, err = request.New(b.Name, common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout), - request.WithLimiter(request.NewBasicRateLimit(bitstampRateInterval, bitstampRequestRate))) + request.WithLimiter(request.NewBasicRateLimit(bitstampRateInterval, bitstampRequestRate, 1))) if err != nil { log.Errorln(log.ExchangeSys, err) } diff --git a/exchanges/btcmarkets/btcmarkets.go b/exchanges/btcmarkets/btcmarkets.go index c1a81c2b8c7..38d91d8d04e 100644 --- a/exchanges/btcmarkets/btcmarkets.go +++ b/exchanges/btcmarkets/btcmarkets.go @@ -814,7 +814,7 @@ func (b *BTCMarkets) SendHTTPRequest(ctx context.Context, path string, result in HTTPDebugging: b.HTTPDebugging, HTTPRecording: b.HTTPRecording, } - return b.SendPayload(ctx, request.Unset, func() (*request.Item, error) { + return b.SendPayload(ctx, request.UnAuth, func() (*request.Item, error) { return item, nil }, request.UnauthenticatedRequest) } diff --git a/exchanges/btcmarkets/btcmarkets_wrapper.go b/exchanges/btcmarkets/btcmarkets_wrapper.go index 3fface3e8e7..5981caaffaa 100644 --- a/exchanges/btcmarkets/btcmarkets_wrapper.go +++ b/exchanges/btcmarkets/btcmarkets_wrapper.go @@ -115,7 +115,7 @@ func (b *BTCMarkets) SetDefaults() { b.Requester, err = request.New(b.Name, common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout), - request.WithLimiter(SetRateLimit())) + request.WithLimiter(GetRateLimit())) if err != nil { log.Errorln(log.ExchangeSys, err) } diff --git a/exchanges/btcmarkets/ratelimit.go b/exchanges/btcmarkets/ratelimit.go index a54aa9665d8..289bec98c4f 100644 --- a/exchanges/btcmarkets/ratelimit.go +++ b/exchanges/btcmarkets/ratelimit.go @@ -1,11 +1,9 @@ package btcmarkets import ( - "context" "time" "github.com/thrasher-corp/gocryptotrader/exchanges/request" - "golang.org/x/time/rate" ) // BTCMarkets Rate limit consts @@ -25,42 +23,14 @@ const ( newReportFunc ) -// RateLimit implements the request.Limiter interface -type RateLimit struct { - Auth *rate.Limiter - UnAuth *rate.Limiter - OrderPlacement *rate.Limiter - BatchOrders *rate.Limiter - WithdrawRequest *rate.Limiter - CreateNewReport *rate.Limiter -} - -// Limit limits the outbound requests -func (r *RateLimit) Limit(ctx context.Context, f request.EndpointLimit) error { - switch f { - case request.Auth: - return r.Auth.Wait(ctx) - case orderFunc: - return r.OrderPlacement.Wait(ctx) - case batchFunc: - return r.BatchOrders.Wait(ctx) - case withdrawFunc: - return r.WithdrawRequest.Wait(ctx) - case newReportFunc: - return r.CreateNewReport.Wait(ctx) - default: - return r.UnAuth.Wait(ctx) - } -} - -// SetRateLimit returns the rate limit for the exchange -func SetRateLimit() *RateLimit { - return &RateLimit{ - Auth: request.NewRateLimit(btcmarketsRateInterval, btcmarketsAuthLimit), - UnAuth: request.NewRateLimit(btcmarketsRateInterval, btcmarketsUnauthLimit), - OrderPlacement: request.NewRateLimit(btcmarketsRateInterval, btcmarketsOrderLimit), - BatchOrders: request.NewRateLimit(btcmarketsRateInterval, btcmarketsBatchOrderLimit), - WithdrawRequest: request.NewRateLimit(btcmarketsRateInterval, btcmarketsWithdrawLimit), - CreateNewReport: request.NewRateLimit(btcmarketsRateInterval, btcmarketsCreateNewReportLimit), +// GetRateLimit returns the rate limit for the exchange +func GetRateLimit() request.RateLimitDefinitions { + return request.RateLimitDefinitions{ + request.Auth: request.NewRateLimitWithWeight(btcmarketsRateInterval, btcmarketsAuthLimit, 1), + request.UnAuth: request.NewRateLimitWithWeight(btcmarketsRateInterval, btcmarketsUnauthLimit, 1), + orderFunc: request.NewRateLimitWithWeight(btcmarketsRateInterval, btcmarketsOrderLimit, 1), + batchFunc: request.NewRateLimitWithWeight(btcmarketsRateInterval, btcmarketsBatchOrderLimit, 1), + withdrawFunc: request.NewRateLimitWithWeight(btcmarketsRateInterval, btcmarketsWithdrawLimit, 1), + newReportFunc: request.NewRateLimitWithWeight(btcmarketsRateInterval, btcmarketsCreateNewReportLimit, 1), } } diff --git a/exchanges/btse/btse_wrapper.go b/exchanges/btse/btse_wrapper.go index 75251f2b155..39b2087b165 100644 --- a/exchanges/btse/btse_wrapper.go +++ b/exchanges/btse/btse_wrapper.go @@ -140,7 +140,7 @@ func (b *BTSE) SetDefaults() { b.Requester, err = request.New(b.Name, common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout), - request.WithLimiter(SetRateLimit())) + request.WithLimiter(GetRateLimit())) if err != nil { log.Errorln(log.ExchangeSys, err) } diff --git a/exchanges/btse/ratelimit.go b/exchanges/btse/ratelimit.go index 4d4bece77cd..0dad19c9940 100644 --- a/exchanges/btse/ratelimit.go +++ b/exchanges/btse/ratelimit.go @@ -1,11 +1,9 @@ package btse import ( - "context" "time" "github.com/thrasher-corp/gocryptotrader/exchanges/request" - "golang.org/x/time/rate" ) const ( @@ -17,26 +15,10 @@ const ( orderFunc ) -// RateLimit implements the request.Limiter interface -type RateLimit struct { - Query *rate.Limiter - Orders *rate.Limiter -} - -// Limit executes rate limiting functionality for exchange -func (r *RateLimit) Limit(ctx context.Context, f request.EndpointLimit) error { - switch f { - case orderFunc: - return r.Orders.Wait(ctx) - default: - return r.Query.Wait(ctx) - } -} - -// SetRateLimit returns the rate limit for the exchange -func SetRateLimit() *RateLimit { - return &RateLimit{ - Orders: request.NewRateLimit(btseRateInterval, btseOrdersLimit), - Query: request.NewRateLimit(btseRateInterval, btseQueryLimit), +// GetRateLimit returns the rate limit for the exchange +func GetRateLimit() request.RateLimitDefinitions { + return request.RateLimitDefinitions{ + orderFunc: request.NewRateLimitWithWeight(btseRateInterval, btseOrdersLimit, 1), + queryFunc: request.NewRateLimitWithWeight(btseRateInterval, btseQueryLimit, 1), } } diff --git a/exchanges/bybit/bybit_wrapper.go b/exchanges/bybit/bybit_wrapper.go index f79495add96..b9138e4a93e 100644 --- a/exchanges/bybit/bybit_wrapper.go +++ b/exchanges/bybit/bybit_wrapper.go @@ -178,7 +178,7 @@ func (by *Bybit) SetDefaults() { by.Requester, err = request.New(by.Name, common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout), - request.WithLimiter(SetRateLimit())) + request.WithLimiter(GetRateLimit())) if err != nil { log.Errorln(log.ExchangeSys, err) } diff --git a/exchanges/bybit/ratelimit.go b/exchanges/bybit/ratelimit.go index 0d2d96b0db8..1bdf90c38cc 100644 --- a/exchanges/bybit/ratelimit.go +++ b/exchanges/bybit/ratelimit.go @@ -1,17 +1,9 @@ package bybit import ( - "context" - "fmt" "time" "github.com/thrasher-corp/gocryptotrader/exchanges/request" - "golang.org/x/time/rate" -) - -const ( - // See: https://bybit-exchange.github.io/docs/v5/rate-limit - spotInterval = time.Second * 5 ) const ( @@ -76,281 +68,67 @@ const ( spotCrossMarginTradeSwitchEPL ) -// RateLimit implements the request.Limiter interface -type RateLimit struct { - SpotRate *rate.Limiter - CreateOrderRate *rate.Limiter - CreateSpotOrderRate *rate.Limiter - AmendOrderRate *rate.Limiter - CancelOrderRate *rate.Limiter - CancelSpotRate *rate.Limiter - CancelAllRate *rate.Limiter - CancelAllSpotRate *rate.Limiter - CreateBatchOrderRate *rate.Limiter - AmendBatchOrderRate *rate.Limiter - CancelBatchOrderRate *rate.Limiter - GetOrderRate *rate.Limiter - GetOrderHistoryRate *rate.Limiter - GetPositionListRate *rate.Limiter - GetExecutionListRate *rate.Limiter - GetPositionClosedPNLRate *rate.Limiter - PostPositionSetLeverageRate *rate.Limiter - SetPositionTPLSModeRate *rate.Limiter - SetPositionRiskLimitRate *rate.Limiter - StopTradingPositionRate *rate.Limiter - GetAccountWalletBalanceRate *rate.Limiter - GetAccountFeeRate *rate.Limiter - GetAssetTransferQueryInfoRate *rate.Limiter - GetAssetTransferQueryTransferCoinListRate *rate.Limiter - GetAssetTransferCoinListRate *rate.Limiter - GetAssetInterTransferListRate *rate.Limiter - GetSubMemberListRate *rate.Limiter - GetAssetUniversalTransferListRate *rate.Limiter - GetAssetAccountCoinBalanceRate *rate.Limiter - GetAssetDepositRecordsRate *rate.Limiter - GetAssetDepositSubMemberRecordsRate *rate.Limiter - GetAssetDepositSubMemberAddressRate *rate.Limiter - GetWithdrawRecordsRate *rate.Limiter - GetAssetCoinInfoRate *rate.Limiter - GetExchangeOrderRecordRate *rate.Limiter - InterTransferRate *rate.Limiter - SaveTransferSubMemberRate *rate.Limiter - UniversalTransferRate *rate.Limiter - CreateWithdrawalRate *rate.Limiter - CancelWithdrawalRate *rate.Limiter - UserCreateSubMemberRate *rate.Limiter - UserCreateSubAPIKeyRate *rate.Limiter - UserFrozenSubMemberRate *rate.Limiter - UserUpdateAPIRate *rate.Limiter - UserUpdateSubAPIRate *rate.Limiter - UserDeleteAPIRate *rate.Limiter - UserDeleteSubAPIRate *rate.Limiter - UserQuerySubMembersRate *rate.Limiter - UserQueryAPIRate *rate.Limiter - GetSpotLeverageTokenOrderRecordsRate *rate.Limiter - SpotLeverageTokenPurchaseRate *rate.Limiter - SpotLeverTokenRedeemRate *rate.Limiter - GetSpotCrossMarginTradeLoanInfoRate *rate.Limiter - GetSpotCrossMarginTradeAccountRate *rate.Limiter - GetSpotCrossMarginTradeOrdersRate *rate.Limiter - GetSpotCrossMarginTradeRepayHistoryRate *rate.Limiter - SpotCrossMarginTradeLoanRate *rate.Limiter - SpotCrossMarginTradeRepayRate *rate.Limiter - SpotCrossMarginTradeSwitchRate *rate.Limiter -} - -// Limit executes rate limiting functionality for Binance -func (r *RateLimit) Limit(ctx context.Context, f request.EndpointLimit) error { - var limiter *rate.Limiter - var tokens int - switch f { - case defaultEPL: - limiter, tokens = r.SpotRate, 1 - case createOrderEPL: - limiter, tokens = r.CreateOrderRate, 10 - case createSpotOrderEPL: - limiter, tokens = r.CreateSpotOrderRate, 20 - case amendOrderEPL: - limiter, tokens = r.AmendOrderRate, 10 - case cancelOrderEPL: - limiter, tokens = r.CancelOrderRate, 10 - case cancelSpotEPL: - limiter, tokens = r.CancelSpotRate, 20 - case cancelAllEPL: - limiter, tokens = r.CancelAllRate, 1 - case cancelAllSpotEPL: - limiter, tokens = r.CancelAllSpotRate, 20 - case createBatchOrderEPL: - limiter, tokens = r.CreateBatchOrderRate, 10 - case amendBatchOrderEPL: - limiter, tokens = r.AmendBatchOrderRate, 10 - case cancelBatchOrderEPL: - limiter, tokens = r.CancelBatchOrderRate, 10 - case getOrderEPL: - limiter, tokens = r.GetOrderRate, 10 - case getOrderHistoryEPL: - limiter, tokens = r.GetOrderHistoryRate, 10 - case getPositionListEPL: - limiter, tokens = r.GetPositionListRate, 10 - case getExecutionListEPL: - limiter, tokens = r.GetExecutionListRate, 10 - case getPositionClosedPNLEPL: - limiter, tokens = r.GetPositionClosedPNLRate, 10 - case postPositionSetLeverageEPL: - limiter, tokens = r.PostPositionSetLeverageRate, 10 - case setPositionTPLSModeEPL: - limiter, tokens = r.SetPositionTPLSModeRate, 10 - case setPositionRiskLimitEPL: - limiter, tokens = r.SetPositionRiskLimitRate, 10 - case stopTradingPositionEPL: - limiter, tokens = r.StopTradingPositionRate, 10 - case getAccountWalletBalanceEPL: - limiter, tokens = r.GetAccountWalletBalanceRate, 10 - case getAccountFeeEPL: - limiter, tokens = r.GetAccountFeeRate, 10 - case getAssetTransferQueryInfoEPL: - limiter, tokens = r.GetAssetTransferQueryInfoRate, 1 - case getAssetTransferQueryTransferCoinListEPL: - limiter, tokens = r.GetAssetTransferQueryTransferCoinListRate, 1 - case getAssetTransferCoinListEPL: - limiter, tokens = r.GetAssetTransferCoinListRate, 1 - case getAssetInterTransferListEPL: - limiter, tokens = r.GetAssetInterTransferListRate, 1 - case getSubMemberListEPL: - limiter, tokens = r.GetSubMemberListRate, 1 - case getAssetUniversalTransferListEPL: - limiter, tokens = r.GetAssetUniversalTransferListRate, 2 - case getAssetAccountCoinBalanceEPL: - limiter, tokens = r.GetAssetAccountCoinBalanceRate, 2 - case getAssetDepositRecordsEPL: - limiter, tokens = r.GetAssetDepositRecordsRate, 1 - case getAssetDepositSubMemberRecordsEPL: - limiter, tokens = r.GetAssetDepositSubMemberRecordsRate, 1 - case getAssetDepositSubMemberAddressEPL: - limiter, tokens = r.GetAssetDepositSubMemberAddressRate, 1 - case getWithdrawRecordsEPL: - limiter, tokens = r.GetWithdrawRecordsRate, 1 - case getAssetCoinInfoEPL: - limiter, tokens = r.GetAssetCoinInfoRate, 1 - case getExchangeOrderRecordEPL: - limiter, tokens = r.GetExchangeOrderRecordRate, 1 - case interTransferEPL: - limiter, tokens = r.InterTransferRate, 1 - case saveTransferSubMemberEPL: - limiter, tokens = r.SaveTransferSubMemberRate, 1 - case universalTransferEPL: - limiter, tokens = r.UniversalTransferRate, 5 - case createWithdrawalEPL: - limiter, tokens = r.CreateWithdrawalRate, 1 - case cancelWithdrawalEPL: - limiter, tokens = r.CancelWithdrawalRate, 1 - case userCreateSubMemberEPL: - limiter, tokens = r.UserCreateSubMemberRate, 5 - case userCreateSubAPIKeyEPL: - limiter, tokens = r.UserCreateSubAPIKeyRate, 5 - case userFrozenSubMemberEPL: - limiter, tokens = r.UserFrozenSubMemberRate, 5 - case userUpdateAPIEPL: - limiter, tokens = r.UserUpdateAPIRate, 5 - case userUpdateSubAPIEPL: - limiter, tokens = r.UserUpdateSubAPIRate, 5 - case userDeleteAPIEPL: - limiter, tokens = r.UserDeleteAPIRate, 5 - case userDeleteSubAPIEPL: - limiter, tokens = r.UserDeleteSubAPIRate, 5 - case userQuerySubMembersEPL: - limiter, tokens = r.UserQuerySubMembersRate, 10 - case userQueryAPIEPL: - limiter, tokens = r.UserQueryAPIRate, 10 - case getSpotLeverageTokenOrderRecordsEPL: - limiter, tokens = r.GetSpotLeverageTokenOrderRecordsRate, 50 - case spotLeverageTokenPurchaseEPL: - limiter, tokens = r.SpotLeverageTokenPurchaseRate, 20 - case spotLeverTokenRedeemEPL: - limiter, tokens = r.SpotLeverTokenRedeemRate, 20 - case getSpotCrossMarginTradeLoanInfoEPL: - limiter, tokens = r.GetSpotCrossMarginTradeLoanInfoRate, 50 - case getSpotCrossMarginTradeAccountEPL: - limiter, tokens = r.GetSpotCrossMarginTradeAccountRate, 50 - case getSpotCrossMarginTradeOrdersEPL: - limiter, tokens = r.GetSpotCrossMarginTradeOrdersRate, 50 - case getSpotCrossMarginTradeRepayHistoryEPL: - limiter, tokens = r.GetSpotCrossMarginTradeRepayHistoryRate, 50 - case spotCrossMarginTradeLoanEPL: - limiter, tokens = r.SpotCrossMarginTradeLoanRate, 50 - case spotCrossMarginTradeRepayEPL: - limiter, tokens = r.SpotCrossMarginTradeRepayRate, 50 - case spotCrossMarginTradeSwitchEPL: - limiter, tokens = r.SpotCrossMarginTradeSwitchRate, 50 - default: - limiter, tokens = r.SpotRate, 1 - } - - var finalDelay time.Duration - var reserves = make([]*rate.Reservation, tokens) - for i := 0; i < tokens; i++ { - // Consume tokens 1 at a time as this avoids needing burst capacity in the limiter, - // which would otherwise allow the rate limit to be exceeded over short periods - reserves[i] = limiter.Reserve() - finalDelay = limiter.Reserve().Delay() - } - - if dl, ok := ctx.Deadline(); ok && dl.Before(time.Now().Add(finalDelay)) { - // Cancel all potential reservations to free up rate limiter if deadline - // is exceeded. - for x := range reserves { - reserves[x].Cancel() - } - return fmt.Errorf("rate limit delay of %s will exceed deadline: %w", - finalDelay, - context.DeadlineExceeded) - } - - time.Sleep(finalDelay) - return nil -} - -// SetRateLimit returns the rate limit for the exchange -func SetRateLimit() *RateLimit { - return &RateLimit{ - SpotRate: request.NewRateLimit(spotInterval, 120), - CreateOrderRate: request.NewRateLimit(time.Second, 10), - CreateSpotOrderRate: request.NewRateLimit(time.Second, 20), - AmendOrderRate: request.NewRateLimit(time.Second, 10), - CancelOrderRate: request.NewRateLimit(time.Second, 10), - CancelSpotRate: request.NewRateLimit(time.Second, 20), - CancelAllRate: request.NewRateLimit(time.Second, 1), - CancelAllSpotRate: request.NewRateLimit(time.Second, 20), - CreateBatchOrderRate: request.NewRateLimit(time.Second, 10), - AmendBatchOrderRate: request.NewRateLimit(time.Second, 10), - CancelBatchOrderRate: request.NewRateLimit(time.Second, 10), - GetOrderRate: request.NewRateLimit(time.Second, 10), - GetOrderHistoryRate: request.NewRateLimit(time.Second, 10), - GetPositionListRate: request.NewRateLimit(time.Second, 10), - GetExecutionListRate: request.NewRateLimit(time.Second, 10), - GetPositionClosedPNLRate: request.NewRateLimit(time.Second, 10), - PostPositionSetLeverageRate: request.NewRateLimit(time.Second, 10), - SetPositionTPLSModeRate: request.NewRateLimit(time.Second, 10), - SetPositionRiskLimitRate: request.NewRateLimit(time.Second, 10), - StopTradingPositionRate: request.NewRateLimit(time.Second, 10), - GetAccountWalletBalanceRate: request.NewRateLimit(time.Second, 10), - GetAccountFeeRate: request.NewRateLimit(time.Second, 10), - GetAssetTransferQueryInfoRate: request.NewRateLimit(time.Minute, 60), - GetAssetTransferQueryTransferCoinListRate: request.NewRateLimit(time.Minute, 60), - GetAssetTransferCoinListRate: request.NewRateLimit(time.Minute, 60), - GetAssetInterTransferListRate: request.NewRateLimit(time.Minute, 60), - GetSubMemberListRate: request.NewRateLimit(time.Minute, 60), - GetAssetUniversalTransferListRate: request.NewRateLimit(time.Second, 2), - GetAssetAccountCoinBalanceRate: request.NewRateLimit(time.Second, 2), - GetAssetDepositRecordsRate: request.NewRateLimit(time.Minute, 300), - GetAssetDepositSubMemberRecordsRate: request.NewRateLimit(time.Minute, 300), - GetAssetDepositSubMemberAddressRate: request.NewRateLimit(time.Minute, 300), - GetWithdrawRecordsRate: request.NewRateLimit(time.Minute, 300), - GetAssetCoinInfoRate: request.NewRateLimit(time.Minute, 300), - GetExchangeOrderRecordRate: request.NewRateLimit(time.Minute, 300), - InterTransferRate: request.NewRateLimit(time.Minute, 20), - SaveTransferSubMemberRate: request.NewRateLimit(time.Minute, 20), - UniversalTransferRate: request.NewRateLimit(time.Second, 5), - CreateWithdrawalRate: request.NewRateLimit(time.Second, 1), - CancelWithdrawalRate: request.NewRateLimit(time.Minute, 60), - UserCreateSubMemberRate: request.NewRateLimit(time.Second, 5), - UserCreateSubAPIKeyRate: request.NewRateLimit(time.Second, 5), - UserFrozenSubMemberRate: request.NewRateLimit(time.Second, 5), - UserUpdateAPIRate: request.NewRateLimit(time.Second, 5), - UserUpdateSubAPIRate: request.NewRateLimit(time.Second, 5), - UserDeleteAPIRate: request.NewRateLimit(time.Second, 5), - UserDeleteSubAPIRate: request.NewRateLimit(time.Second, 5), - UserQuerySubMembersRate: request.NewRateLimit(time.Second, 10), - UserQueryAPIRate: request.NewRateLimit(time.Second, 10), - GetSpotLeverageTokenOrderRecordsRate: request.NewRateLimit(time.Second, 50), - SpotLeverageTokenPurchaseRate: request.NewRateLimit(time.Second, 20), - SpotLeverTokenRedeemRate: request.NewRateLimit(time.Second, 20), - GetSpotCrossMarginTradeLoanInfoRate: request.NewRateLimit(time.Second, 50), - GetSpotCrossMarginTradeAccountRate: request.NewRateLimit(time.Second, 50), - GetSpotCrossMarginTradeOrdersRate: request.NewRateLimit(time.Second, 50), - GetSpotCrossMarginTradeRepayHistoryRate: request.NewRateLimit(time.Second, 50), - SpotCrossMarginTradeLoanRate: request.NewRateLimit(time.Second, 20), - SpotCrossMarginTradeRepayRate: request.NewRateLimit(time.Second, 20), - SpotCrossMarginTradeSwitchRate: request.NewRateLimit(time.Second, 20), +// GetRateLimit returns the rate limit for the exchange +func GetRateLimit() request.RateLimitDefinitions { + return request.RateLimitDefinitions{ + defaultEPL: request.NewRateLimitWithWeight(time.Second*5 /* See: https://bybit-exchange.github.io/docs/v5/rate-limit */, 600, 1), + createOrderEPL: request.NewRateLimitWithWeight(time.Second, 10, 10), + createSpotOrderEPL: request.NewRateLimitWithWeight(time.Second, 20, 20), + amendOrderEPL: request.NewRateLimitWithWeight(time.Second, 10, 10), + cancelOrderEPL: request.NewRateLimitWithWeight(time.Second, 10, 10), + cancelSpotEPL: request.NewRateLimitWithWeight(time.Second, 20, 20), + cancelAllEPL: request.NewRateLimitWithWeight(time.Second, 1, 1), + cancelAllSpotEPL: request.NewRateLimitWithWeight(time.Second, 20, 20), + createBatchOrderEPL: request.NewRateLimitWithWeight(time.Second, 10, 10), + amendBatchOrderEPL: request.NewRateLimitWithWeight(time.Second, 10, 10), + cancelBatchOrderEPL: request.NewRateLimitWithWeight(time.Second, 10, 10), + getOrderEPL: request.NewRateLimitWithWeight(time.Second, 10, 10), + getOrderHistoryEPL: request.NewRateLimitWithWeight(time.Second, 10, 10), + getPositionListEPL: request.NewRateLimitWithWeight(time.Second, 10, 10), + getExecutionListEPL: request.NewRateLimitWithWeight(time.Second, 10, 10), + getPositionClosedPNLEPL: request.NewRateLimitWithWeight(time.Second, 10, 10), + postPositionSetLeverageEPL: request.NewRateLimitWithWeight(time.Second, 10, 10), + setPositionTPLSModeEPL: request.NewRateLimitWithWeight(time.Second, 10, 10), + setPositionRiskLimitEPL: request.NewRateLimitWithWeight(time.Second, 10, 10), + stopTradingPositionEPL: request.NewRateLimitWithWeight(time.Second, 10, 10), + getAccountWalletBalanceEPL: request.NewRateLimitWithWeight(time.Second, 10, 10), + getAccountFeeEPL: request.NewRateLimitWithWeight(time.Second, 10, 10), + getAssetTransferQueryInfoEPL: request.NewRateLimitWithWeight(time.Minute, 60, 1), + getAssetTransferQueryTransferCoinListEPL: request.NewRateLimitWithWeight(time.Minute, 60, 1), + getAssetTransferCoinListEPL: request.NewRateLimitWithWeight(time.Minute, 60, 1), + getAssetInterTransferListEPL: request.NewRateLimitWithWeight(time.Minute, 60, 1), + getSubMemberListEPL: request.NewRateLimitWithWeight(time.Minute, 60, 1), + getAssetUniversalTransferListEPL: request.NewRateLimitWithWeight(time.Second, 2, 2), + getAssetAccountCoinBalanceEPL: request.NewRateLimitWithWeight(time.Second, 2, 2), + getAssetDepositRecordsEPL: request.NewRateLimitWithWeight(time.Minute, 30, 1), + getAssetDepositSubMemberRecordsEPL: request.NewRateLimitWithWeight(time.Minute, 30, 1), + getAssetDepositSubMemberAddressEPL: request.NewRateLimitWithWeight(time.Minute, 30, 1), + getWithdrawRecordsEPL: request.NewRateLimitWithWeight(time.Minute, 30, 1), + getAssetCoinInfoEPL: request.NewRateLimitWithWeight(time.Minute, 30, 1), + getExchangeOrderRecordEPL: request.NewRateLimitWithWeight(time.Minute, 30, 1), + interTransferEPL: request.NewRateLimitWithWeight(time.Minute, 20, 1), + saveTransferSubMemberEPL: request.NewRateLimitWithWeight(time.Minute, 20, 1), + universalTransferEPL: request.NewRateLimitWithWeight(time.Second, 5, 5), + createWithdrawalEPL: request.NewRateLimitWithWeight(time.Second, 1, 1), + cancelWithdrawalEPL: request.NewRateLimitWithWeight(time.Minute, 60, 1), + userCreateSubMemberEPL: request.NewRateLimitWithWeight(time.Second, 5, 5), + userCreateSubAPIKeyEPL: request.NewRateLimitWithWeight(time.Second, 5, 5), + userFrozenSubMemberEPL: request.NewRateLimitWithWeight(time.Second, 5, 5), + userUpdateAPIEPL: request.NewRateLimitWithWeight(time.Second, 5, 5), + userUpdateSubAPIEPL: request.NewRateLimitWithWeight(time.Second, 5, 5), + userDeleteAPIEPL: request.NewRateLimitWithWeight(time.Second, 5, 5), + userDeleteSubAPIEPL: request.NewRateLimitWithWeight(time.Second, 5, 5), + userQuerySubMembersEPL: request.NewRateLimitWithWeight(time.Second, 10, 10), + userQueryAPIEPL: request.NewRateLimitWithWeight(time.Second, 10, 10), + getSpotLeverageTokenOrderRecordsEPL: request.NewRateLimitWithWeight(time.Second, 50, 50), + spotLeverageTokenPurchaseEPL: request.NewRateLimitWithWeight(time.Second, 20, 20), + spotLeverTokenRedeemEPL: request.NewRateLimitWithWeight(time.Second, 20, 20), + getSpotCrossMarginTradeLoanInfoEPL: request.NewRateLimitWithWeight(time.Second, 50, 50), + getSpotCrossMarginTradeAccountEPL: request.NewRateLimitWithWeight(time.Second, 50, 50), + getSpotCrossMarginTradeOrdersEPL: request.NewRateLimitWithWeight(time.Second, 50, 50), + getSpotCrossMarginTradeRepayHistoryEPL: request.NewRateLimitWithWeight(time.Second, 50, 50), + spotCrossMarginTradeLoanEPL: request.NewRateLimitWithWeight(time.Second, 20, 50), + spotCrossMarginTradeRepayEPL: request.NewRateLimitWithWeight(time.Second, 20, 50), + spotCrossMarginTradeSwitchEPL: request.NewRateLimitWithWeight(time.Second, 20, 50), } } diff --git a/exchanges/coinbasepro/coinbasepro.go b/exchanges/coinbasepro/coinbasepro.go index 18ec2f097d7..31308e77ae7 100644 --- a/exchanges/coinbasepro/coinbasepro.go +++ b/exchanges/coinbasepro/coinbasepro.go @@ -767,7 +767,7 @@ func (c *CoinbasePro) SendHTTPRequest(ctx context.Context, ep exchange.URL, path HTTPRecording: c.HTTPRecording, } - return c.SendPayload(ctx, request.Unset, func() (*request.Item, error) { + return c.SendPayload(ctx, request.UnAuth, func() (*request.Item, error) { return item, nil }, request.UnauthenticatedRequest) } diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index f334fe5b438..14d837280aa 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -109,7 +109,7 @@ func (c *CoinbasePro) SetDefaults() { c.Requester, err = request.New(c.Name, common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout), - request.WithLimiter(SetRateLimit())) + request.WithLimiter(GetRateLimit())) if err != nil { log.Errorln(log.ExchangeSys, err) } diff --git a/exchanges/coinbasepro/ratelimit.go b/exchanges/coinbasepro/ratelimit.go index d9c6fa76f61..e636f6660d9 100644 --- a/exchanges/coinbasepro/ratelimit.go +++ b/exchanges/coinbasepro/ratelimit.go @@ -1,11 +1,9 @@ package coinbasepro import ( - "context" "time" "github.com/thrasher-corp/gocryptotrader/exchanges/request" - "golang.org/x/time/rate" ) // Coinbasepro rate limit conts @@ -15,24 +13,10 @@ const ( coinbaseproUnauthRate = 2 ) -// RateLimit implements the request.Limiter interface -type RateLimit struct { - Auth *rate.Limiter - UnAuth *rate.Limiter -} - -// Limit limits outbound calls -func (r *RateLimit) Limit(ctx context.Context, f request.EndpointLimit) error { - if f == request.Auth { - return r.Auth.Wait(ctx) - } - return r.UnAuth.Wait(ctx) -} - -// SetRateLimit returns the rate limit for the exchange -func SetRateLimit() *RateLimit { - return &RateLimit{ - Auth: request.NewRateLimit(coinbaseproRateInterval, coinbaseproAuthRate), - UnAuth: request.NewRateLimit(coinbaseproRateInterval, coinbaseproUnauthRate), +// GetRateLimit returns the rate limit for the exchange +func GetRateLimit() request.RateLimitDefinitions { + return request.RateLimitDefinitions{ + request.Auth: request.NewRateLimitWithWeight(coinbaseproRateInterval, coinbaseproAuthRate, 1), + request.UnAuth: request.NewRateLimitWithWeight(coinbaseproRateInterval, coinbaseproUnauthRate, 1), } } diff --git a/exchanges/deribit/deribit_wrapper.go b/exchanges/deribit/deribit_wrapper.go index 3b3bef964d1..faa7050a716 100644 --- a/exchanges/deribit/deribit_wrapper.go +++ b/exchanges/deribit/deribit_wrapper.go @@ -152,7 +152,7 @@ func (d *Deribit) SetDefaults() { } d.Requester, err = request.New(d.Name, common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout), - request.WithLimiter(SetRateLimit()), + request.WithLimiter(GetRateLimits()), ) if err != nil { log.Errorln(log.ExchangeSys, err) diff --git a/exchanges/deribit/ratelimit.go b/exchanges/deribit/ratelimit.go index 563bde1a413..642b43b6923 100644 --- a/exchanges/deribit/ratelimit.go +++ b/exchanges/deribit/ratelimit.go @@ -1,79 +1,32 @@ package deribit import ( - "context" - "fmt" "time" "github.com/thrasher-corp/gocryptotrader/exchanges/request" - "golang.org/x/time/rate" ) const ( - // request rates per interval + // Request rates per interval minMatchingBurst = 100 nonMatchingRate = 20 - minMatchingRate = 5 portfoliMarginRate = 1 - + // Weightings + matchingWeight = 5 + standardWeight = 1 + // Rate limit keys nonMatchingEPL request.EndpointLimit = iota matchingEPL portfolioMarginEPL privatePortfolioMarginEPL ) -// RateLimiter holds the rate limiter to endpoints -type RateLimiter struct { - NonMatchingEngine *rate.Limiter - MatchingEngine *rate.Limiter - PortfolioMargin *rate.Limiter - PrivatePortfolioMargin *rate.Limiter -} - -// SetRateLimit returns the rate limit for the exchange -func SetRateLimit() *RateLimiter { - return &RateLimiter{ - NonMatchingEngine: request.NewRateLimit(time.Second, nonMatchingRate), - MatchingEngine: request.NewRateLimit(time.Second, minMatchingBurst), - PortfolioMargin: request.NewRateLimit(5*time.Second, portfoliMarginRate), - PrivatePortfolioMargin: request.NewRateLimit(5*time.Second, portfoliMarginRate), - } -} - -// Limit executes rate limiting functionality for Binance -func (r *RateLimiter) Limit(ctx context.Context, f request.EndpointLimit) error { - var limiter *rate.Limiter - var tokens int - switch f { - case nonMatchingEPL: - limiter, tokens = r.NonMatchingEngine, 1 - case portfolioMarginEPL: - limiter, tokens = r.PortfolioMargin, portfoliMarginRate - case privatePortfolioMarginEPL: - limiter, tokens = r.PrivatePortfolioMargin, portfoliMarginRate - default: - limiter, tokens = r.MatchingEngine, minMatchingRate - } - var finalDelay time.Duration - var reserves = make([]*rate.Reservation, tokens) - for i := 0; i < tokens; i++ { - // Consume tokens 1 at a time as this avoids needing burst capacity in the limiter, - // which would otherwise allow the rate limit to be exceeded over short periods - reserves[i] = limiter.Reserve() - finalDelay = reserves[i].Delay() +// GetRateLimits returns the rate limit for the exchange +func GetRateLimits() request.RateLimitDefinitions { + return request.RateLimitDefinitions{ + nonMatchingEPL: request.GetRateLimiterWithWeight(request.NewRateLimit(time.Second, nonMatchingRate), standardWeight), + matchingEPL: request.GetRateLimiterWithWeight(request.NewRateLimit(time.Second, minMatchingBurst), matchingWeight), + portfolioMarginEPL: request.GetRateLimiterWithWeight(request.NewRateLimit(5*time.Second, portfoliMarginRate), standardWeight), + privatePortfolioMarginEPL: request.GetRateLimiterWithWeight(request.NewRateLimit(5*time.Second, portfoliMarginRate), standardWeight), } - - if dl, ok := ctx.Deadline(); ok && dl.Before(time.Now().Add(finalDelay)) { - // Cancel all potential reservations to free up rate limiter if deadline - // is exceeded. - for x := range reserves { - reserves[x].Cancel() - } - return fmt.Errorf("rate limit delay of %s will exceed deadline: %w", - finalDelay, - context.DeadlineExceeded) - } - - time.Sleep(finalDelay) - return nil } diff --git a/exchanges/exmo/exmo_wrapper.go b/exchanges/exmo/exmo_wrapper.go index d6d6c3e93c9..dbb8f7235f6 100644 --- a/exchanges/exmo/exmo_wrapper.go +++ b/exchanges/exmo/exmo_wrapper.go @@ -90,7 +90,7 @@ func (e *EXMO) SetDefaults() { e.Requester, err = request.New(e.Name, common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout), - request.WithLimiter(request.NewBasicRateLimit(exmoRateInterval, exmoRequestRate))) + request.WithLimiter(request.NewBasicRateLimit(exmoRateInterval, exmoRequestRate, 1))) if err != nil { log.Errorln(log.ExchangeSys, err) } diff --git a/exchanges/gateio/gateio_wrapper.go b/exchanges/gateio/gateio_wrapper.go index a3e364e0a7b..bb62857bfd7 100644 --- a/exchanges/gateio/gateio_wrapper.go +++ b/exchanges/gateio/gateio_wrapper.go @@ -142,7 +142,7 @@ func (g *Gateio) SetDefaults() { } g.Requester, err = request.New(g.Name, common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout), - request.WithLimiter(SetRateLimit()), + request.WithLimiter(GetRateLimit()), ) if err != nil { log.Errorln(log.ExchangeSys, err) diff --git a/exchanges/gateio/ratelimiter.go b/exchanges/gateio/ratelimiter.go index 0d024095397..2c554b38e5d 100644 --- a/exchanges/gateio/ratelimiter.go +++ b/exchanges/gateio/ratelimiter.go @@ -1,12 +1,9 @@ package gateio import ( - "context" - "fmt" "time" "github.com/thrasher-corp/gocryptotrader/exchanges/request" - "golang.org/x/time/rate" ) // GateIO endpoints limits. @@ -40,79 +37,18 @@ const ( threeSecondsInterval = time.Second * 3 ) -// RateLimitter represents a rate limiter structure for gateIO endpoints. -type RateLimitter struct { - SpotDefault *rate.Limiter - SpotPrivate *rate.Limiter - SpotPlaceOrders *rate.Limiter - SpotCancelOrders *rate.Limiter - PerpetualSwapDefault *rate.Limiter - PerpetualSwapPlaceOrders *rate.Limiter - PerpetualSwapPrivate *rate.Limiter - PerpetualSwapCancelOrders *rate.Limiter - Wallet *rate.Limiter - Withdrawal *rate.Limiter -} - -// Limit executes rate limiting functionality -// implements the request.Limiter interface -func (r *RateLimitter) Limit(ctx context.Context, epl request.EndpointLimit) error { - var limiter *rate.Limiter - var tokens int - switch epl { - case spotDefaultEPL: - limiter, tokens = r.SpotDefault, 1 - case spotPrivateEPL: - return r.SpotPrivate.Wait(ctx) - case spotPlaceOrdersEPL: - return r.SpotPlaceOrders.Wait(ctx) - case spotCancelOrdersEPL: - return r.SpotCancelOrders.Wait(ctx) - case perpetualSwapDefaultEPL: - limiter, tokens = r.PerpetualSwapDefault, 1 - case perpetualSwapPlaceOrdersEPL: - return r.PerpetualSwapPlaceOrders.Wait(ctx) - case perpetualSwapPrivateEPL: - return r.PerpetualSwapPrivate.Wait(ctx) - case perpetualSwapCancelOrdersEPL: - return r.PerpetualSwapCancelOrders.Wait(ctx) - case walletEPL: - return r.Wallet.Wait(ctx) - case withdrawalEPL: - return r.Withdrawal.Wait(ctx) - default: - } - var finalDelay time.Duration - var reserves = make([]*rate.Reservation, tokens) - for i := 0; i < tokens; i++ { - reserves[i] = limiter.Reserve() - finalDelay = reserves[i].Delay() - } - if dl, ok := ctx.Deadline(); ok && dl.Before(time.Now().Add(finalDelay)) { - for x := range reserves { - reserves[x].Cancel() - } - return fmt.Errorf("rate limit delay of %s will exceed deadline: %w", - finalDelay, - context.DeadlineExceeded) - } - - time.Sleep(finalDelay) - return nil -} - -// SetRateLimit returns the rate limiter for the exchange -func SetRateLimit() *RateLimitter { - return &RateLimitter{ - SpotDefault: request.NewRateLimit(oneSecondInterval, spotPublicRate), - SpotPrivate: request.NewRateLimit(oneSecondInterval, spotPrivateRate), - SpotPlaceOrders: request.NewRateLimit(oneSecondInterval, spotPlaceOrdersRate), - SpotCancelOrders: request.NewRateLimit(oneSecondInterval, spotCancelOrdersRate), - PerpetualSwapDefault: request.NewRateLimit(oneSecondInterval, perpetualSwapPublicRate), - PerpetualSwapPlaceOrders: request.NewRateLimit(oneSecondInterval, perpetualSwapPlaceOrdersRate), - PerpetualSwapPrivate: request.NewRateLimit(oneSecondInterval, perpetualSwapPrivateRate), - PerpetualSwapCancelOrders: request.NewRateLimit(oneSecondInterval, perpetualSwapCancelOrdersRate), - Wallet: request.NewRateLimit(oneSecondInterval, walletRate), - Withdrawal: request.NewRateLimit(threeSecondsInterval, withdrawalRate), +// GetRateLimit returns the rate limiter for the exchange +func GetRateLimit() request.RateLimitDefinitions { + return request.RateLimitDefinitions{ + spotDefaultEPL: request.NewRateLimitWithWeight(oneSecondInterval, spotPublicRate, 1), + spotPrivateEPL: request.NewRateLimitWithWeight(oneSecondInterval, spotPrivateRate, 1), + spotPlaceOrdersEPL: request.NewRateLimitWithWeight(oneSecondInterval, spotPlaceOrdersRate, 1), + spotCancelOrdersEPL: request.NewRateLimitWithWeight(oneSecondInterval, spotCancelOrdersRate, 1), + perpetualSwapDefaultEPL: request.NewRateLimitWithWeight(oneSecondInterval, perpetualSwapPublicRate, 1), + perpetualSwapPlaceOrdersEPL: request.NewRateLimitWithWeight(oneSecondInterval, perpetualSwapPlaceOrdersRate, 1), + perpetualSwapPrivateEPL: request.NewRateLimitWithWeight(oneSecondInterval, perpetualSwapPrivateRate, 1), + perpetualSwapCancelOrdersEPL: request.NewRateLimitWithWeight(oneSecondInterval, perpetualSwapCancelOrdersRate, 1), + walletEPL: request.NewRateLimitWithWeight(oneSecondInterval, walletRate, 1), + withdrawalEPL: request.NewRateLimitWithWeight(threeSecondsInterval, withdrawalRate, 1), } } diff --git a/exchanges/gemini/gemini.go b/exchanges/gemini/gemini.go index fb23666b647..e6cce565302 100644 --- a/exchanges/gemini/gemini.go +++ b/exchanges/gemini/gemini.go @@ -402,7 +402,7 @@ func (g *Gemini) SendHTTPRequest(ctx context.Context, ep exchange.URL, path stri HTTPRecording: g.HTTPRecording, } - return g.SendPayload(ctx, request.Unset, func() (*request.Item, error) { + return g.SendPayload(ctx, request.UnAuth, func() (*request.Item, error) { return item, nil }, request.UnauthenticatedRequest) } diff --git a/exchanges/gemini/gemini_wrapper.go b/exchanges/gemini/gemini_wrapper.go index 424e08acb23..ed9b0f8fb1b 100644 --- a/exchanges/gemini/gemini_wrapper.go +++ b/exchanges/gemini/gemini_wrapper.go @@ -93,7 +93,7 @@ func (g *Gemini) SetDefaults() { g.Requester, err = request.New(g.Name, common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout), - request.WithLimiter(SetRateLimit())) + request.WithLimiter(GetRateLimit())) if err != nil { log.Errorln(log.ExchangeSys, err) } diff --git a/exchanges/gemini/ratelimit.go b/exchanges/gemini/ratelimit.go index d9afe87368b..f64d42a58a6 100644 --- a/exchanges/gemini/ratelimit.go +++ b/exchanges/gemini/ratelimit.go @@ -1,11 +1,9 @@ package gemini import ( - "context" "time" "github.com/thrasher-corp/gocryptotrader/exchanges/request" - "golang.org/x/time/rate" ) const ( @@ -15,24 +13,10 @@ const ( geminiUnauthRate = 120 ) -// RateLimit implements the request.Limiter interface -type RateLimit struct { - Auth *rate.Limiter - UnAuth *rate.Limiter -} - -// Limit limits the endpoint functionality -func (r *RateLimit) Limit(ctx context.Context, f request.EndpointLimit) error { - if f == request.Auth { - return r.Auth.Wait(ctx) - } - return r.UnAuth.Wait(ctx) -} - -// SetRateLimit returns the rate limit for the exchange -func SetRateLimit() *RateLimit { - return &RateLimit{ - Auth: request.NewRateLimit(geminiRateInterval, geminiAuthRate), - UnAuth: request.NewRateLimit(geminiRateInterval, geminiUnauthRate), +// GetRateLimit returns the rate limit for the exchange +func GetRateLimit() request.RateLimitDefinitions { + return request.RateLimitDefinitions{ + request.Auth: request.NewRateLimitWithWeight(geminiRateInterval, geminiAuthRate, 1), + request.UnAuth: request.NewRateLimitWithWeight(geminiRateInterval, geminiUnauthRate, 1), } } diff --git a/exchanges/hitbtc/hitbtc_wrapper.go b/exchanges/hitbtc/hitbtc_wrapper.go index ae5971377f2..83030b89de5 100644 --- a/exchanges/hitbtc/hitbtc_wrapper.go +++ b/exchanges/hitbtc/hitbtc_wrapper.go @@ -112,7 +112,7 @@ func (h *HitBTC) SetDefaults() { h.Requester, err = request.New(h.Name, common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout), - request.WithLimiter(SetRateLimit())) + request.WithLimiter(GetRateLimit())) if err != nil { log.Errorln(log.ExchangeSys, err) } diff --git a/exchanges/hitbtc/ratelimit.go b/exchanges/hitbtc/ratelimit.go index 9ae58f5b592..bd0b6d54006 100644 --- a/exchanges/hitbtc/ratelimit.go +++ b/exchanges/hitbtc/ratelimit.go @@ -1,12 +1,9 @@ package hitbtc import ( - "context" - "errors" "time" "github.com/thrasher-corp/gocryptotrader/exchanges/request" - "golang.org/x/time/rate" ) const ( @@ -20,32 +17,11 @@ const ( otherRequests ) -// RateLimit implements the request.Limiter interface -type RateLimit struct { - MarketData *rate.Limiter - Trading *rate.Limiter - Other *rate.Limiter -} - -// Limit limits outbound requests -func (r *RateLimit) Limit(ctx context.Context, f request.EndpointLimit) error { - switch f { - case marketRequests: - return r.MarketData.Wait(ctx) - case tradingRequests: - return r.Trading.Wait(ctx) - case otherRequests: - return r.Other.Wait(ctx) - default: - return errors.New("functionality not found") - } -} - -// SetRateLimit returns the rate limit for the exchange -func SetRateLimit() *RateLimit { - return &RateLimit{ - MarketData: request.NewRateLimit(hitbtcRateInterval, hitbtcMarketDataReqRate), - Trading: request.NewRateLimit(hitbtcRateInterval, hitbtcTradingReqRate), - Other: request.NewRateLimit(hitbtcRateInterval, hitbtcAllOthers), +// GetRateLimit returns the rate limit for the exchange +func GetRateLimit() request.RateLimitDefinitions { + return request.RateLimitDefinitions{ + marketRequests: request.NewRateLimitWithWeight(hitbtcRateInterval, hitbtcMarketDataReqRate, 1), + tradingRequests: request.NewRateLimitWithWeight(hitbtcRateInterval, hitbtcTradingReqRate, 1), + otherRequests: request.NewRateLimitWithWeight(hitbtcRateInterval, hitbtcAllOthers, 1), } } diff --git a/exchanges/huobi/huobi_wrapper.go b/exchanges/huobi/huobi_wrapper.go index 93391c1a7ef..a9c34628f93 100644 --- a/exchanges/huobi/huobi_wrapper.go +++ b/exchanges/huobi/huobi_wrapper.go @@ -165,7 +165,7 @@ func (h *HUOBI) SetDefaults() { h.Requester, err = request.New(h.Name, common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout), - request.WithLimiter(SetRateLimit())) + request.WithLimiter(GetRateLimit())) if err != nil { log.Errorln(log.ExchangeSys, err) } diff --git a/exchanges/huobi/ratelimit.go b/exchanges/huobi/ratelimit.go index 55866fe2c66..6c828f63c5f 100644 --- a/exchanges/huobi/ratelimit.go +++ b/exchanges/huobi/ratelimit.go @@ -1,11 +1,9 @@ package huobi import ( - "context" "time" "github.com/thrasher-corp/gocryptotrader/exchanges/request" - "golang.org/x/time/rate" ) const ( @@ -28,47 +26,17 @@ const ( huobiFuturesUnAuth huobiFuturesTransfer huobiSwapAuth - huobiSwapUnauth + huobiSwapUnAuth ) -// RateLimit implements the request.Limiter interface -type RateLimit struct { - Spot *rate.Limiter - FuturesAuth *rate.Limiter - FuturesUnauth *rate.Limiter - SwapAuth *rate.Limiter - SwapUnauth *rate.Limiter - FuturesXfer *rate.Limiter -} - -// Limit limits outbound requests -func (r *RateLimit) Limit(ctx context.Context, f request.EndpointLimit) error { - switch f { - // TODO: Add futures and swap functionality - case huobiFuturesAuth: - return r.FuturesAuth.Wait(ctx) - case huobiFuturesUnAuth: - return r.FuturesUnauth.Wait(ctx) - case huobiFuturesTransfer: - return r.FuturesXfer.Wait(ctx) - case huobiSwapAuth: - return r.SwapAuth.Wait(ctx) - case huobiSwapUnauth: - return r.SwapUnauth.Wait(ctx) - default: - // Spot calls - return r.Spot.Wait(ctx) - } -} - -// SetRateLimit returns the rate limit for the exchange -func SetRateLimit() *RateLimit { - return &RateLimit{ - Spot: request.NewRateLimit(huobiSpotRateInterval, huobiSpotRequestRate), - FuturesAuth: request.NewRateLimit(huobiFuturesRateInterval, huobiFuturesAuthRequestRate), - FuturesUnauth: request.NewRateLimit(huobiFuturesRateInterval, huobiFuturesUnAuthRequestRate), - SwapAuth: request.NewRateLimit(huobiSwapRateInterval, huobiSwapAuthRequestRate), - SwapUnauth: request.NewRateLimit(huobiSwapRateInterval, huobiSwapUnauthRequestRate), - FuturesXfer: request.NewRateLimit(huobiFuturesTransferRateInterval, huobiFuturesTransferReqRate), +// GetRateLimit returns the rate limit for the exchange +func GetRateLimit() request.RateLimitDefinitions { + return request.RateLimitDefinitions{ + request.Unset: request.NewRateLimitWithWeight(huobiSpotRateInterval, huobiSpotRequestRate, 1), + huobiFuturesAuth: request.NewRateLimitWithWeight(huobiFuturesRateInterval, huobiFuturesAuthRequestRate, 1), + huobiFuturesUnAuth: request.NewRateLimitWithWeight(huobiFuturesRateInterval, huobiFuturesUnAuthRequestRate, 1), + huobiSwapAuth: request.NewRateLimitWithWeight(huobiSwapRateInterval, huobiSwapAuthRequestRate, 1), + huobiSwapUnAuth: request.NewRateLimitWithWeight(huobiSwapRateInterval, huobiSwapUnauthRequestRate, 1), + huobiFuturesTransfer: request.NewRateLimitWithWeight(huobiFuturesTransferRateInterval, huobiFuturesTransferReqRate, 1), } } diff --git a/exchanges/kraken/kraken_wrapper.go b/exchanges/kraken/kraken_wrapper.go index 64e459cfe30..05bede1b841 100644 --- a/exchanges/kraken/kraken_wrapper.go +++ b/exchanges/kraken/kraken_wrapper.go @@ -172,7 +172,7 @@ func (k *Kraken) SetDefaults() { k.Requester, err = request.New(k.Name, common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout), - request.WithLimiter(request.NewBasicRateLimit(krakenRateInterval, krakenRequestRate))) + request.WithLimiter(request.NewBasicRateLimit(krakenRateInterval, krakenRequestRate, 1))) if err != nil { log.Errorln(log.ExchangeSys, err) } diff --git a/exchanges/kucoin/kucoin_ratelimit.go b/exchanges/kucoin/kucoin_ratelimit.go index 7f8a52b47c1..62140ee7b78 100644 --- a/exchanges/kucoin/kucoin_ratelimit.go +++ b/exchanges/kucoin/kucoin_ratelimit.go @@ -1,13 +1,9 @@ package kucoin import ( - "context" - "errors" - "fmt" "time" "github.com/thrasher-corp/gocryptotrader/exchanges/request" - "golang.org/x/time/rate" ) const ( @@ -15,39 +11,6 @@ const ( oneMinuteInterval = time.Minute ) -// RateLimit implements the request.Limiter interface -type RateLimit struct { - RetrieveAccountLedger *rate.Limiter - MasterSubUserTransfer *rate.Limiter - RetrieveDepositList *rate.Limiter - RetrieveV1HistoricalDepositList *rate.Limiter - RetrieveWithdrawalList *rate.Limiter - RetrieveV1HistoricalWithdrawalList *rate.Limiter - PlaceOrder *rate.Limiter - PlaceMarginOrders *rate.Limiter - PlaceBulkOrders *rate.Limiter - CancelOrder *rate.Limiter - CancelAllOrders *rate.Limiter - ListOrders *rate.Limiter - ListFills *rate.Limiter - RetrieveFullOrderbook *rate.Limiter - RetrieveMarginAccount *rate.Limiter - SpotRate *rate.Limiter - FuturesRate *rate.Limiter - - FRetrieveAccountOverviewRate *rate.Limiter - FRetrieveTransactionHistoryRate *rate.Limiter - FPlaceOrderRate *rate.Limiter - FCancelAnOrderRate *rate.Limiter - FLimitOrderMassCancelationRate *rate.Limiter - FRetrieveOrderListRate *rate.Limiter - FRetrieveFillsRate *rate.Limiter - FRecentFillsRate *rate.Limiter - FRetrievePositionListRate *rate.Limiter - FRetrieveFundingHistoryRate *rate.Limiter - FRetrieveFullOrderbookLevel2Rate *rate.Limiter -} - // rate of request per interval const ( retrieveAccountLedgerRate = 18 @@ -116,129 +79,41 @@ const ( defaultFuturesEPL ) -// Limit executes rate limiting functionality for Kucoin -func (r *RateLimit) Limit(ctx context.Context, epl request.EndpointLimit) error { - var limiter *rate.Limiter - var tokens int - switch epl { - case retrieveAccountLedgerEPL: - return r.RetrieveAccountLedger.Wait(ctx) - case masterSubUserTransferEPL: - return r.MasterSubUserTransfer.Wait(ctx) - case retrieveDepositListEPL: - return r.RetrieveDepositList.Wait(ctx) - case retrieveV1HistoricalDepositListEPL: - return r.RetrieveV1HistoricalDepositList.Wait(ctx) - case retrieveWithdrawalListEPL: - return r.RetrieveWithdrawalList.Wait(ctx) - case retrieveV1HistoricalWithdrawalListEPL: - return r.RetrieveV1HistoricalWithdrawalList.Wait(ctx) - case placeOrderEPL: - return r.PlaceOrder.Wait(ctx) - case placeMarginOrdersEPL: - return r.PlaceMarginOrders.Wait(ctx) - case placeBulkOrdersEPL: - return r.PlaceBulkOrders.Wait(ctx) - case cancelOrderEPL: - return r.CancelOrder.Wait(ctx) - case cancelAllOrdersEPL: - return r.CancelAllOrders.Wait(ctx) - case listOrdersEPL: - return r.ListOrders.Wait(ctx) - case listFillsEPL: - return r.ListFills.Wait(ctx) - case retrieveFullOrderbookEPL: - return r.RetrieveFullOrderbook.Wait(ctx) - case retrieveMarginAccountEPL: - return r.RetrieveMarginAccount.Wait(ctx) - case futuresRetrieveAccountOverviewEPL: - return r.FRetrieveAccountOverviewRate.Wait(ctx) - case futuresRetrieveTransactionHistoryEPL: - return r.FRetrieveTransactionHistoryRate.Wait(ctx) - case futuresPlaceOrderEPL: - return r.FPlaceOrderRate.Wait(ctx) - case futuresCancelAnOrderEPL: - return r.FCancelAnOrderRate.Wait(ctx) - case futuresLimitOrderMassCancelationEPL: - return r.FLimitOrderMassCancelationRate.Wait(ctx) - case futuresRetrieveOrderListEPL: - return r.FRetrieveOrderListRate.Wait(ctx) - case futuresRetrieveFillsEPL: - return r.FRetrieveFillsRate.Wait(ctx) - case futuresRecentFillsEPL: - return r.FRecentFillsRate.Wait(ctx) - case futuresRetrievePositionListEPL: - return r.FRetrievePositionListRate.Wait(ctx) - case futuresRetrieveFundingHistoryEPL: - return r.FRetrieveFundingHistoryRate.Wait(ctx) - case futuresRetrieveFullOrderbookLevel2EPL: - return r.FRetrieveFullOrderbookLevel2Rate.Wait(ctx) - case defaultSpotEPL: - limiter, tokens = r.SpotRate, 1 - case defaultFuturesEPL: - limiter, tokens = r.FuturesRate, 1 - default: - return errors.New("endpoint rate limit functionality not found") - } - var finalDelay time.Duration - var reserves = make([]*rate.Reservation, tokens) - for i := 0; i < tokens; i++ { - // Consume tokens 1 at a time as this avoids needing burst capacity in the limiter, - // which would otherwise allow the rate limit to be exceeded over short periods - reserves[i] = limiter.Reserve() - finalDelay = reserves[i].Delay() - } - - if dl, ok := ctx.Deadline(); ok && dl.Before(time.Now().Add(finalDelay)) { - // Cancel all potential reservations to free up rate limiter if deadline - // is exceeded. - for x := range reserves { - reserves[x].Cancel() - } - return fmt.Errorf("rate limit delay of %s will exceed deadline: %w", - finalDelay, - context.DeadlineExceeded) - } - - time.Sleep(finalDelay) - return nil -} - -// SetRateLimit returns a RateLimit instance, which implements the request.Limiter interface. -func SetRateLimit() *RateLimit { - return &RateLimit{ +// GetRateLimit returns a RateLimit instance, which implements the request.Limiter interface. +func GetRateLimit() request.RateLimitDefinitions { + return request.RateLimitDefinitions{ // spot specific rate limiters - RetrieveAccountLedger: request.NewRateLimit(threeSecondsInterval, retrieveAccountLedgerRate), - MasterSubUserTransfer: request.NewRateLimit(threeSecondsInterval, masterSubUserTransferRate), - RetrieveDepositList: request.NewRateLimit(threeSecondsInterval, retrieveDepositListRate), - RetrieveV1HistoricalDepositList: request.NewRateLimit(threeSecondsInterval, retrieveV1HistoricalDepositListRate), - RetrieveWithdrawalList: request.NewRateLimit(threeSecondsInterval, retrieveWithdrawalListRate), - RetrieveV1HistoricalWithdrawalList: request.NewRateLimit(threeSecondsInterval, retrieveV1HistoricalWithdrawalListRate), - PlaceOrder: request.NewRateLimit(threeSecondsInterval, placeOrderRate), - PlaceMarginOrders: request.NewRateLimit(threeSecondsInterval, placeMarginOrdersRate), - PlaceBulkOrders: request.NewRateLimit(threeSecondsInterval, placeBulkOrdersRate), - CancelOrder: request.NewRateLimit(threeSecondsInterval, cancelOrderRate), - CancelAllOrders: request.NewRateLimit(threeSecondsInterval, cancelAllOrdersRate), - ListOrders: request.NewRateLimit(threeSecondsInterval, listOrdersRate), - ListFills: request.NewRateLimit(threeSecondsInterval, listFillsRate), - RetrieveFullOrderbook: request.NewRateLimit(threeSecondsInterval, retrieveFullOrderbookRate), - RetrieveMarginAccount: request.NewRateLimit(threeSecondsInterval, retrieveMarginAccountRate), + retrieveAccountLedgerEPL: request.NewRateLimitWithWeight(threeSecondsInterval, retrieveAccountLedgerRate, 1), + masterSubUserTransferEPL: request.NewRateLimitWithWeight(threeSecondsInterval, masterSubUserTransferRate, 1), + retrieveDepositListEPL: request.NewRateLimitWithWeight(threeSecondsInterval, retrieveDepositListRate, 1), + retrieveV1HistoricalDepositListEPL: request.NewRateLimitWithWeight(threeSecondsInterval, retrieveV1HistoricalDepositListRate, 1), + retrieveWithdrawalListEPL: request.NewRateLimitWithWeight(threeSecondsInterval, retrieveWithdrawalListRate, 1), + retrieveV1HistoricalWithdrawalListEPL: request.NewRateLimitWithWeight(threeSecondsInterval, retrieveV1HistoricalWithdrawalListRate, 1), + placeOrderEPL: request.NewRateLimitWithWeight(threeSecondsInterval, placeOrderRate, 1), + placeMarginOrdersEPL: request.NewRateLimitWithWeight(threeSecondsInterval, placeMarginOrdersRate, 1), + placeBulkOrdersEPL: request.NewRateLimitWithWeight(threeSecondsInterval, placeBulkOrdersRate, 1), + cancelOrderEPL: request.NewRateLimitWithWeight(threeSecondsInterval, cancelOrderRate, 1), + cancelAllOrdersEPL: request.NewRateLimitWithWeight(threeSecondsInterval, cancelAllOrdersRate, 1), + listOrdersEPL: request.NewRateLimitWithWeight(threeSecondsInterval, listOrdersRate, 1), + listFillsEPL: request.NewRateLimitWithWeight(threeSecondsInterval, listFillsRate, 1), + retrieveFullOrderbookEPL: request.NewRateLimitWithWeight(threeSecondsInterval, retrieveFullOrderbookRate, 1), + retrieveMarginAccountEPL: request.NewRateLimitWithWeight(threeSecondsInterval, retrieveMarginAccountRate, 1), // default spot and futures rates - SpotRate: request.NewRateLimit(oneMinuteInterval, defaultSpotRate), - FuturesRate: request.NewRateLimit(oneMinuteInterval, defaultFuturesRate), + defaultSpotEPL: request.NewRateLimitWithWeight(oneMinuteInterval, defaultSpotRate, 1), + defaultFuturesEPL: request.NewRateLimitWithWeight(oneMinuteInterval, defaultFuturesRate, 1), // futures specific rate limiters - FRetrieveAccountOverviewRate: request.NewRateLimit(threeSecondsInterval, futuresRetrieveAccountOverviewRate), - FRetrieveTransactionHistoryRate: request.NewRateLimit(threeSecondsInterval, futuresRetrieveTransactionHistoryRate), - FPlaceOrderRate: request.NewRateLimit(threeSecondsInterval, futuresPlaceOrderRate), - FCancelAnOrderRate: request.NewRateLimit(threeSecondsInterval, futuresCancelAnOrderRate), - FLimitOrderMassCancelationRate: request.NewRateLimit(threeSecondsInterval, futuresLimitOrderMassCancelationRate), - FRetrieveOrderListRate: request.NewRateLimit(threeSecondsInterval, futuresRetrieveOrderListRate), - FRetrieveFillsRate: request.NewRateLimit(threeSecondsInterval, futuresRetrieveFillsRate), - FRecentFillsRate: request.NewRateLimit(threeSecondsInterval, futuresRecentFillsRate), - FRetrievePositionListRate: request.NewRateLimit(threeSecondsInterval, futuresRetrievePositionListRate), - FRetrieveFundingHistoryRate: request.NewRateLimit(threeSecondsInterval, futuresRetrieveFundingHistoryRate), - FRetrieveFullOrderbookLevel2Rate: request.NewRateLimit(threeSecondsInterval, futuresRetrieveFullOrderbookLevel2Rate), + futuresRetrieveAccountOverviewEPL: request.NewRateLimitWithWeight(threeSecondsInterval, futuresRetrieveAccountOverviewRate, 1), + futuresRetrieveTransactionHistoryEPL: request.NewRateLimitWithWeight(threeSecondsInterval, futuresRetrieveTransactionHistoryRate, 1), + futuresPlaceOrderEPL: request.NewRateLimitWithWeight(threeSecondsInterval, futuresPlaceOrderRate, 1), + futuresCancelAnOrderEPL: request.NewRateLimitWithWeight(threeSecondsInterval, futuresCancelAnOrderRate, 1), + futuresLimitOrderMassCancelationEPL: request.NewRateLimitWithWeight(threeSecondsInterval, futuresLimitOrderMassCancelationRate, 1), + futuresRetrieveOrderListEPL: request.NewRateLimitWithWeight(threeSecondsInterval, futuresRetrieveOrderListRate, 1), + futuresRetrieveFillsEPL: request.NewRateLimitWithWeight(threeSecondsInterval, futuresRetrieveFillsRate, 1), + futuresRecentFillsEPL: request.NewRateLimitWithWeight(threeSecondsInterval, futuresRecentFillsRate, 1), + futuresRetrievePositionListEPL: request.NewRateLimitWithWeight(threeSecondsInterval, futuresRetrievePositionListRate, 1), + futuresRetrieveFundingHistoryEPL: request.NewRateLimitWithWeight(threeSecondsInterval, futuresRetrieveFundingHistoryRate, 1), + futuresRetrieveFullOrderbookLevel2EPL: request.NewRateLimitWithWeight(threeSecondsInterval, futuresRetrieveFullOrderbookLevel2Rate, 1), } } diff --git a/exchanges/kucoin/kucoin_wrapper.go b/exchanges/kucoin/kucoin_wrapper.go index 49773b17d76..cc6849ae3f8 100644 --- a/exchanges/kucoin/kucoin_wrapper.go +++ b/exchanges/kucoin/kucoin_wrapper.go @@ -159,7 +159,7 @@ func (ku *Kucoin) SetDefaults() { } ku.Requester, err = request.New(ku.Name, common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout), - request.WithLimiter(SetRateLimit())) + request.WithLimiter(GetRateLimit())) if err != nil { log.Errorln(log.ExchangeSys, err) } diff --git a/exchanges/okcoin/okcoin_ratelimit.go b/exchanges/okcoin/okcoin_ratelimit.go index 00bfa9b77a9..965ab768f10 100644 --- a/exchanges/okcoin/okcoin_ratelimit.go +++ b/exchanges/okcoin/okcoin_ratelimit.go @@ -1,12 +1,9 @@ package okcoin import ( - "context" - "errors" "time" "github.com/thrasher-corp/gocryptotrader/exchanges/request" - "golang.org/x/time/rate" ) // Interval instances @@ -16,79 +13,6 @@ const ( fiveSecondsInterval = time.Second * 5 ) -// RateLimit implementa a rate Limiter -type RateLimit struct { - PlaceTradeOrder *rate.Limiter - PlaceTradeMultipleOrders *rate.Limiter - CancelTradeOrder *rate.Limiter - CancelMultipleOrder *rate.Limiter - AmendTradeOrder *rate.Limiter - AmendMultipleOrders *rate.Limiter - GetOrderDetails *rate.Limiter - GetOrderList *rate.Limiter - GetOrderHistory *rate.Limiter - GetOrderhistory3Months *rate.Limiter - GetTransactionDetails3Days *rate.Limiter - GetTransactionDetails3Months *rate.Limiter - PlaceAlgoOrder *rate.Limiter - CancelAlgoOrder *rate.Limiter - CancelAdvancedAlgoOrder *rate.Limiter - GetAlgoOrderList *rate.Limiter - GetAlgoOrderHistory *rate.Limiter - GetFundingCurrencies *rate.Limiter - GetFundingAccountBalance *rate.Limiter - GetAccountAssetValuation *rate.Limiter - FundingTransfer *rate.Limiter - GetFundsTransferState *rate.Limiter - AssetBillsDetail *rate.Limiter - LightningDeposits *rate.Limiter - GetAssetDepositAddress *rate.Limiter - GetDepositHistory *rate.Limiter - PostWithdrawal *rate.Limiter - PostLightningWithdrawal *rate.Limiter - CancelWithdrawal *rate.Limiter - GetAssetWithdrawalHistory *rate.Limiter - GetAccountBalance *rate.Limiter - GetBillsDetailLast3Month *rate.Limiter - GetBillsDetail *rate.Limiter - GetAccountConfiguration *rate.Limiter - GetMaxBuySellAmountOpenAmount *rate.Limiter - GetMaxAvailableTradableAmount *rate.Limiter - GetFeeRates *rate.Limiter - GetMaxWithdrawals *rate.Limiter - GetAvailablePairs *rate.Limiter - RequestQuotes *rate.Limiter - PlaceRFQOrder *rate.Limiter - GetRFQTradeOrderDetails *rate.Limiter - GetRFQTradeOrderHistory *rate.Limiter - FiatDepositRate *rate.Limiter - FiatCancelDepositRate *rate.Limiter - FiatDepositHistoryRate *rate.Limiter - FiatWithdrawalRate *rate.Limiter - FiatCancelWithdrawalRate *rate.Limiter - FiatGetWithdrawalsRate *rate.Limiter - FiatGetChannelInfoRate *rate.Limiter - SubAccountsList *rate.Limiter - GetAPIKeyOfASubAccount *rate.Limiter - GetSubAccountTradingBalance *rate.Limiter - GetSubAccountFundingBalance *rate.Limiter - SubAccountTransferHistory *rate.Limiter - MasterAccountsManageTransfersBetweenSubaccount *rate.Limiter - GetTickers *rate.Limiter - GetTicker *rate.Limiter - GetOrderbook *rate.Limiter - GetCandlesticks *rate.Limiter - GetCandlestickHistory *rate.Limiter - GetPublicTrades *rate.Limiter - GetPublicTradeHistrory *rate.Limiter - Get24HourTradingVolume *rate.Limiter - GetOracle *rate.Limiter - GetExchangeRate *rate.Limiter - GetInstrumentsRate *rate.Limiter - GetSystemTimeRate *rate.Limiter - GetSystemStatusRate *rate.Limiter -} - // Rate of requests per interval for each end point const ( placeTradeOrderRate = 60 @@ -239,223 +163,77 @@ const ( getSystemStatusEPL ) -// Limit implements an endpoint limit. -func (r *RateLimit) Limit(ctx context.Context, ep request.EndpointLimit) error { - switch ep { - case placeTradeOrderEPL: - return r.PlaceTradeOrder.Wait(ctx) - case placeTradeMultipleOrdersEPL: - return r.PlaceTradeMultipleOrders.Wait(ctx) - case cancelTradeOrderEPL: - return r.CancelTradeOrder.Wait(ctx) - case cancelMultipleOrderEPL: - return r.CancelMultipleOrder.Wait(ctx) - case amendTradeOrderEPL: - return r.AmendTradeOrder.Wait(ctx) - case amendMultipleOrdersEPL: - return r.AmendMultipleOrders.Wait(ctx) - case getOrderDetailsEPL: - return r.GetOrderDetails.Wait(ctx) - case getOrderListEPL: - return r.GetOrderList.Wait(ctx) - case getOrderHistoryEPL: - return r.GetOrderHistory.Wait(ctx) - case getOrderhistory3MonthsEPL: - return r.GetOrderhistory3Months.Wait(ctx) - case getTransactionDetails3DaysEPL: - return r.GetTransactionDetails3Days.Wait(ctx) - case getTransactionDetails3MonthsEPL: - return r.GetTransactionDetails3Months.Wait(ctx) - case placeAlgoOrderEPL: - return r.PlaceAlgoOrder.Wait(ctx) - case cancelAlgoOrderEPL: - return r.CancelAlgoOrder.Wait(ctx) - case cancelAdvancedAlgoOrderEPL: - return r.CancelAdvancedAlgoOrder.Wait(ctx) - case getAlgoOrderListEPL: - return r.GetAlgoOrderList.Wait(ctx) - case getAlgoOrderHistoryEPL: - return r.GetAlgoOrderHistory.Wait(ctx) - case getFundingCurrenciesEPL: - return r.GetFundingCurrencies.Wait(ctx) - case getFundingAccountBalanceEPL: - return r.GetFundingAccountBalance.Wait(ctx) - case getAccountAssetValuationEPL: - return r.GetAccountAssetValuation.Wait(ctx) - case fundingTransferEPL: - return r.FundingTransfer.Wait(ctx) - case getFundsTransferStateEPL: - return r.GetFundsTransferState.Wait(ctx) - case assetBillsDetailEPL: - return r.AssetBillsDetail.Wait(ctx) - case lightningDepositsEPL: - return r.LightningDeposits.Wait(ctx) - case getAssetDepositAddressEPL: - return r.GetAssetDepositAddress.Wait(ctx) - case getDepositHistoryEPL: - return r.GetDepositHistory.Wait(ctx) - case postWithdrawalEPL: - return r.PostWithdrawal.Wait(ctx) - case postLightningWithdrawalEPL: - return r.PostLightningWithdrawal.Wait(ctx) - case cancelWithdrawalEPL: - return r.CancelWithdrawal.Wait(ctx) - case getAssetWithdrawalHistoryEPL: - return r.GetAssetWithdrawalHistory.Wait(ctx) - case getAccountBalanceEPL: - return r.GetAccountBalance.Wait(ctx) - case getBillsDetailLast3MonthEPL: - return r.GetBillsDetailLast3Month.Wait(ctx) - case getBillsDetailEPL: - return r.GetBillsDetail.Wait(ctx) - case getAccountConfigurationEPL: - return r.GetAccountConfiguration.Wait(ctx) - case getMaxBuySellAmountOpenAmountEPL: - return r.GetMaxBuySellAmountOpenAmount.Wait(ctx) - case getMaxAvailableTradableAmountEPL: - return r.GetMaxAvailableTradableAmount.Wait(ctx) - case getFeeRatesEPL: - return r.GetFeeRates.Wait(ctx) - case getMaxWithdrawalsEPL: - return r.GetMaxWithdrawals.Wait(ctx) - case getAvailablePairsEPL: - return r.GetAvailablePairs.Wait(ctx) - case requestQuotesEPL: - return r.RequestQuotes.Wait(ctx) - case placeRFQOrderEPL: - return r.PlaceRFQOrder.Wait(ctx) - case getRFQTradeOrderDetailsEPL: - return r.GetRFQTradeOrderDetails.Wait(ctx) - case getRFQTradeOrderHistoryEPL: - return r.GetRFQTradeOrderHistory.Wait(ctx) - case fiatDepositEPL: - return r.FiatDepositRate.Wait(ctx) - case fiatCancelDepositEPL: - return r.FiatCancelDepositRate.Wait(ctx) - case fiatDepositHistoryEPL: - return r.FiatDepositHistoryRate.Wait(ctx) - case fiatWithdrawalEPL: - return r.FiatWithdrawalRate.Wait(ctx) - case fiatCancelWithdrawalEPL: - return r.FiatCancelWithdrawalRate.Wait(ctx) - case fiatGetWithdrawalsEPL: - return r.FiatGetWithdrawalsRate.Wait(ctx) - case fiatGetChannelInfoEPL: - return r.FiatGetChannelInfoRate.Wait(ctx) - case subAccountsListEPL: - return r.SubAccountsList.Wait(ctx) - case getAPIKeyOfASubAccountEPL: - return r.GetAPIKeyOfASubAccount.Wait(ctx) - case getSubAccountTradingBalanceEPL: - return r.GetSubAccountTradingBalance.Wait(ctx) - case getSubAccountFundingBalanceEPL: - return r.GetSubAccountFundingBalance.Wait(ctx) - case subAccountTransferHistoryEPL: - return r.SubAccountTransferHistory.Wait(ctx) - case masterAccountsManageTransfersBetweenSubaccountEPL: - return r.MasterAccountsManageTransfersBetweenSubaccount.Wait(ctx) - case getTickersEPL: - return r.GetTickers.Wait(ctx) - case getTickerEPL: - return r.GetTicker.Wait(ctx) - case getOrderbookEPL: - return r.GetOrderbook.Wait(ctx) - case getCandlesticksEPL: - return r.GetCandlesticks.Wait(ctx) - case getCandlestickHistoryEPL: - return r.GetCandlestickHistory.Wait(ctx) - case getPublicTradesEPL: - return r.GetPublicTrades.Wait(ctx) - case getPublicTradeHistroryEPL: - return r.GetPublicTradeHistrory.Wait(ctx) - case get24HourTradingVolumeEPL: - return r.Get24HourTradingVolume.Wait(ctx) - case getOracleEPL: - return r.GetOracle.Wait(ctx) - case getExchangeRateEPL: - return r.GetExchangeRate.Wait(ctx) - case getInstrumentsEPL: - return r.GetInstrumentsRate.Wait(ctx) - case getSystemTimeEPL: - return r.GetSystemTimeRate.Wait(ctx) - case getSystemStatusEPL: - return r.GetSystemStatusRate.Wait(ctx) - default: - return errors.New("unknown endpoint limit") - } -} - -// SetRateLimit returns a new RateLimit instance which implements request.Limiter interface. -func SetRateLimit() *RateLimit { - return &RateLimit{ - PlaceTradeOrder: request.NewRateLimit(twoSecondsInterval, placeTradeOrderRate), - PlaceTradeMultipleOrders: request.NewRateLimit(twoSecondsInterval, placeTradeMultipleOrdersRate), - CancelTradeOrder: request.NewRateLimit(twoSecondsInterval, cancelTradeOrderRate), - CancelMultipleOrder: request.NewRateLimit(twoSecondsInterval, cancelMultipleOrderRate), - AmendTradeOrder: request.NewRateLimit(twoSecondsInterval, amendTradeOrderRate), - AmendMultipleOrders: request.NewRateLimit(twoSecondsInterval, amendMultipleOrdersRate), - GetOrderDetails: request.NewRateLimit(twoSecondsInterval, getOrderDetailsRate), - GetOrderList: request.NewRateLimit(twoSecondsInterval, getOrderListRate), - GetOrderHistory: request.NewRateLimit(twoSecondsInterval, getOrderHistoryRate), - GetOrderhistory3Months: request.NewRateLimit(twoSecondsInterval, getOrderhistory3MonthsRate), - GetTransactionDetails3Days: request.NewRateLimit(twoSecondsInterval, getTransactionDetails3DaysRate), - GetTransactionDetails3Months: request.NewRateLimit(twoSecondsInterval, getTransactionDetails3MonthsRate), - PlaceAlgoOrder: request.NewRateLimit(twoSecondsInterval, placeAlgoOrderRate), - CancelAlgoOrder: request.NewRateLimit(twoSecondsInterval, cancelAlgoOrderRate), - CancelAdvancedAlgoOrder: request.NewRateLimit(twoSecondsInterval, cancelAdvancedAlgoOrderRate), - GetAlgoOrderList: request.NewRateLimit(twoSecondsInterval, getAlgoOrderListRate), - GetAlgoOrderHistory: request.NewRateLimit(twoSecondsInterval, getAlgoOrderHistoryRate), - GetFundingCurrencies: request.NewRateLimit(oneSecondInterval, getFundingCurrenciesRate), - GetFundingAccountBalance: request.NewRateLimit(oneSecondInterval, getFundingAccountBalanceRate), - GetAccountAssetValuation: request.NewRateLimit(twoSecondsInterval, getAccountAssetValuationRate), - FundingTransfer: request.NewRateLimit(oneSecondInterval, fundingTransferRate), - GetFundsTransferState: request.NewRateLimit(oneSecondInterval, getFundsTransferStateRate), - AssetBillsDetail: request.NewRateLimit(oneSecondInterval, assetBillsDetailRate), - LightningDeposits: request.NewRateLimit(oneSecondInterval, lightningDepositsRate), - GetAssetDepositAddress: request.NewRateLimit(oneSecondInterval, getAssetDepositAddressRate), - GetDepositHistory: request.NewRateLimit(oneSecondInterval, getDepositHistoryRate), - PostWithdrawal: request.NewRateLimit(oneSecondInterval, postWithdrawalRate), - PostLightningWithdrawal: request.NewRateLimit(oneSecondInterval, postLightningWithdrawalRate), - CancelWithdrawal: request.NewRateLimit(oneSecondInterval, cancelWithdrawalRate), - GetAssetWithdrawalHistory: request.NewRateLimit(oneSecondInterval, getAssetWithdrawalHistoryRate), - GetAccountBalance: request.NewRateLimit(twoSecondsInterval, getAccountBalanceRate), - GetBillsDetailLast3Month: request.NewRateLimit(oneSecondInterval, getBillsDetailLast3MonthRate), - GetBillsDetail: request.NewRateLimit(oneSecondInterval, getBillsDetailRate), - GetAccountConfiguration: request.NewRateLimit(twoSecondsInterval, getAccountConfigurationRate), - GetMaxBuySellAmountOpenAmount: request.NewRateLimit(twoSecondsInterval, getMaxBuySellAmountOpenAmountRate), - GetMaxAvailableTradableAmount: request.NewRateLimit(twoSecondsInterval, getMaxAvailableTradableAmountRate), - GetFeeRates: request.NewRateLimit(twoSecondsInterval, getFeeRatesRate), - GetMaxWithdrawals: request.NewRateLimit(twoSecondsInterval, getMaxWithdrawalsRate), - GetAvailablePairs: request.NewRateLimit(oneSecondInterval, getAvailablePairsRate), - RequestQuotes: request.NewRateLimit(oneSecondInterval, requestQuotesRate), - PlaceRFQOrder: request.NewRateLimit(oneSecondInterval, placeRFQOrderRate), - GetRFQTradeOrderDetails: request.NewRateLimit(oneSecondInterval, getRFQTradeOrderDetailsRate), - GetRFQTradeOrderHistory: request.NewRateLimit(oneSecondInterval, getRFQTradeOrderHistoryRate), - FiatDepositRate: request.NewRateLimit(oneSecondInterval, fiatDepositRate), - FiatCancelDepositRate: request.NewRateLimit(twoSecondsInterval, fiatCancelDepositRate), - FiatDepositHistoryRate: request.NewRateLimit(oneSecondInterval, fiatDepositHistoryRate), - FiatWithdrawalRate: request.NewRateLimit(oneSecondInterval, fiatWithdrawalRate), - FiatCancelWithdrawalRate: request.NewRateLimit(twoSecondsInterval, fiatCancelWithdrawalRate), - FiatGetWithdrawalsRate: request.NewRateLimit(oneSecondInterval, fiatGetWithdrawalsRate), - FiatGetChannelInfoRate: request.NewRateLimit(oneSecondInterval, fiatGetChannelInfoRate), - SubAccountsList: request.NewRateLimit(twoSecondsInterval, subAccountsListRate), - GetAPIKeyOfASubAccount: request.NewRateLimit(oneSecondInterval, getAPIKeyOfASubAccountRate), - GetSubAccountTradingBalance: request.NewRateLimit(twoSecondsInterval, getSubAccountTradingBalanceRate), - GetSubAccountFundingBalance: request.NewRateLimit(twoSecondsInterval, getSubAccountFundingBalanceRate), - SubAccountTransferHistory: request.NewRateLimit(oneSecondInterval, subAccountTransferHistoryRate), - MasterAccountsManageTransfersBetweenSubaccount: request.NewRateLimit(oneSecondInterval, masterAccountsManageTransfersBetweenSubaccountRate), - GetTickers: request.NewRateLimit(twoSecondsInterval, getTickersRate), - GetTicker: request.NewRateLimit(twoSecondsInterval, getTickerRate), - GetOrderbook: request.NewRateLimit(twoSecondsInterval, getOrderbookRate), - GetCandlesticks: request.NewRateLimit(twoSecondsInterval, getCandlesticksRate), - GetCandlestickHistory: request.NewRateLimit(twoSecondsInterval, getCandlestickHistoryRate), - GetPublicTrades: request.NewRateLimit(twoSecondsInterval, getPublicTradesRate), - GetPublicTradeHistrory: request.NewRateLimit(twoSecondsInterval, getPublicTradeHistroryRate), - Get24HourTradingVolume: request.NewRateLimit(twoSecondsInterval, get24HourTradingVolumeRate), - GetOracle: request.NewRateLimit(fiveSecondsInterval, getOracleRate), - GetExchangeRate: request.NewRateLimit(twoSecondsInterval, getExchangeRateRate), - GetInstrumentsRate: request.NewRateLimit(twoSecondsInterval, getInstrumentsRate), - GetSystemTimeRate: request.NewRateLimit(twoSecondsInterval, getSystemTimeRate), - GetSystemStatusRate: request.NewRateLimit(fiveSecondsInterval, getSystemStatusRate), +// GetRateLimit returns a new RateLimit instance which implements request.Limiter interface. +func GetRateLimit() request.RateLimitDefinitions { + return request.RateLimitDefinitions{ + placeTradeOrderEPL: request.NewRateLimitWithWeight(twoSecondsInterval, placeTradeOrderRate, 1), + placeTradeMultipleOrdersEPL: request.NewRateLimitWithWeight(twoSecondsInterval, placeTradeMultipleOrdersRate, 1), + cancelTradeOrderEPL: request.NewRateLimitWithWeight(twoSecondsInterval, cancelTradeOrderRate, 1), + cancelMultipleOrderEPL: request.NewRateLimitWithWeight(twoSecondsInterval, cancelMultipleOrderRate, 1), + amendTradeOrderEPL: request.NewRateLimitWithWeight(twoSecondsInterval, amendTradeOrderRate, 1), + amendMultipleOrdersEPL: request.NewRateLimitWithWeight(twoSecondsInterval, amendMultipleOrdersRate, 1), + getOrderDetailsEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getOrderDetailsRate, 1), + getOrderListEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getOrderListRate, 1), + getOrderHistoryEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getOrderHistoryRate, 1), + getOrderhistory3MonthsEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getOrderhistory3MonthsRate, 1), + getTransactionDetails3DaysEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getTransactionDetails3DaysRate, 1), + getTransactionDetails3MonthsEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getTransactionDetails3MonthsRate, 1), + placeAlgoOrderEPL: request.NewRateLimitWithWeight(twoSecondsInterval, placeAlgoOrderRate, 1), + cancelAlgoOrderEPL: request.NewRateLimitWithWeight(twoSecondsInterval, cancelAlgoOrderRate, 1), + cancelAdvancedAlgoOrderEPL: request.NewRateLimitWithWeight(twoSecondsInterval, cancelAdvancedAlgoOrderRate, 1), + getAlgoOrderListEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getAlgoOrderListRate, 1), + getAlgoOrderHistoryEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getAlgoOrderHistoryRate, 1), + getFundingCurrenciesEPL: request.NewRateLimitWithWeight(oneSecondInterval, getFundingCurrenciesRate, 1), + getFundingAccountBalanceEPL: request.NewRateLimitWithWeight(oneSecondInterval, getFundingAccountBalanceRate, 1), + getAccountAssetValuationEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getAccountAssetValuationRate, 1), + fundingTransferEPL: request.NewRateLimitWithWeight(oneSecondInterval, fundingTransferRate, 1), + getFundsTransferStateEPL: request.NewRateLimitWithWeight(oneSecondInterval, getFundsTransferStateRate, 1), + assetBillsDetailEPL: request.NewRateLimitWithWeight(oneSecondInterval, assetBillsDetailRate, 1), + lightningDepositsEPL: request.NewRateLimitWithWeight(oneSecondInterval, lightningDepositsRate, 1), + getAssetDepositAddressEPL: request.NewRateLimitWithWeight(oneSecondInterval, getAssetDepositAddressRate, 1), + getDepositHistoryEPL: request.NewRateLimitWithWeight(oneSecondInterval, getDepositHistoryRate, 1), + postWithdrawalEPL: request.NewRateLimitWithWeight(oneSecondInterval, postWithdrawalRate, 1), + postLightningWithdrawalEPL: request.NewRateLimitWithWeight(oneSecondInterval, postLightningWithdrawalRate, 1), + cancelWithdrawalEPL: request.NewRateLimitWithWeight(oneSecondInterval, cancelWithdrawalRate, 1), + getAssetWithdrawalHistoryEPL: request.NewRateLimitWithWeight(oneSecondInterval, getAssetWithdrawalHistoryRate, 1), + getAccountBalanceEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getAccountBalanceRate, 1), + getBillsDetailLast3MonthEPL: request.NewRateLimitWithWeight(oneSecondInterval, getBillsDetailLast3MonthRate, 1), + getBillsDetailEPL: request.NewRateLimitWithWeight(oneSecondInterval, getBillsDetailRate, 1), + getAccountConfigurationEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getAccountConfigurationRate, 1), + getMaxBuySellAmountOpenAmountEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getMaxBuySellAmountOpenAmountRate, 1), + getMaxAvailableTradableAmountEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getMaxAvailableTradableAmountRate, 1), + getFeeRatesEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getFeeRatesRate, 1), + getMaxWithdrawalsEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getMaxWithdrawalsRate, 1), + getAvailablePairsEPL: request.NewRateLimitWithWeight(oneSecondInterval, getAvailablePairsRate, 1), + requestQuotesEPL: request.NewRateLimitWithWeight(oneSecondInterval, requestQuotesRate, 1), + placeRFQOrderEPL: request.NewRateLimitWithWeight(oneSecondInterval, placeRFQOrderRate, 1), + getRFQTradeOrderDetailsEPL: request.NewRateLimitWithWeight(oneSecondInterval, getRFQTradeOrderDetailsRate, 1), + getRFQTradeOrderHistoryEPL: request.NewRateLimitWithWeight(oneSecondInterval, getRFQTradeOrderHistoryRate, 1), + fiatDepositEPL: request.NewRateLimitWithWeight(oneSecondInterval, fiatDepositRate, 1), + fiatCancelDepositEPL: request.NewRateLimitWithWeight(twoSecondsInterval, fiatCancelDepositRate, 1), + fiatDepositHistoryEPL: request.NewRateLimitWithWeight(oneSecondInterval, fiatDepositHistoryRate, 1), + fiatWithdrawalEPL: request.NewRateLimitWithWeight(oneSecondInterval, fiatWithdrawalRate, 1), + fiatCancelWithdrawalEPL: request.NewRateLimitWithWeight(twoSecondsInterval, fiatCancelWithdrawalRate, 1), + fiatGetWithdrawalsEPL: request.NewRateLimitWithWeight(oneSecondInterval, fiatGetWithdrawalsRate, 1), + fiatGetChannelInfoEPL: request.NewRateLimitWithWeight(oneSecondInterval, fiatGetChannelInfoRate, 1), + subAccountsListEPL: request.NewRateLimitWithWeight(twoSecondsInterval, subAccountsListRate, 1), + getAPIKeyOfASubAccountEPL: request.NewRateLimitWithWeight(oneSecondInterval, getAPIKeyOfASubAccountRate, 1), + getSubAccountTradingBalanceEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getSubAccountTradingBalanceRate, 1), + getSubAccountFundingBalanceEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getSubAccountFundingBalanceRate, 1), + subAccountTransferHistoryEPL: request.NewRateLimitWithWeight(oneSecondInterval, subAccountTransferHistoryRate, 1), + masterAccountsManageTransfersBetweenSubaccountEPL: request.NewRateLimitWithWeight(oneSecondInterval, masterAccountsManageTransfersBetweenSubaccountRate, 1), + getTickersEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getTickersRate, 1), + getTickerEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getTickerRate, 1), + getOrderbookEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getOrderbookRate, 1), + getCandlesticksEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getCandlesticksRate, 1), + getCandlestickHistoryEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getCandlestickHistoryRate, 1), + getPublicTradesEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getPublicTradesRate, 1), + getPublicTradeHistroryEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getPublicTradeHistroryRate, 1), + get24HourTradingVolumeEPL: request.NewRateLimitWithWeight(twoSecondsInterval, get24HourTradingVolumeRate, 1), + getOracleEPL: request.NewRateLimitWithWeight(fiveSecondsInterval, getOracleRate, 1), + getExchangeRateEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getExchangeRateRate, 1), + getInstrumentsEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getInstrumentsRate, 1), + getSystemTimeEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getSystemTimeRate, 1), + getSystemStatusEPL: request.NewRateLimitWithWeight(fiveSecondsInterval, getSystemStatusRate, 1), } } diff --git a/exchanges/okcoin/okcoin_wrapper.go b/exchanges/okcoin/okcoin_wrapper.go index bda68cf2c59..a86b88f543d 100644 --- a/exchanges/okcoin/okcoin_wrapper.go +++ b/exchanges/okcoin/okcoin_wrapper.go @@ -116,7 +116,7 @@ func (o *Okcoin) SetDefaults() { } o.Requester, err = request.New(o.Name, common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout), - request.WithLimiter(SetRateLimit()), + request.WithLimiter(GetRateLimit()), ) if err != nil { log.Errorln(log.ExchangeSys, err) diff --git a/exchanges/okx/okx.go b/exchanges/okx/okx.go index 2e951367947..af782f4d2f4 100644 --- a/exchanges/okx/okx.go +++ b/exchanges/okx/okx.go @@ -3140,7 +3140,7 @@ func (ok *Okx) GetIntervalEnum(interval kline.Interval, appendUTC bool) string { // GetCandlesticks Retrieve the candlestick charts. This endpoint can retrieve the latest 1,440 data entries. Charts are returned in groups based on the requested bar. func (ok *Okx) GetCandlesticks(ctx context.Context, instrumentID string, interval kline.Interval, before, after time.Time, limit int64) ([]CandleStick, error) { - return ok.GetCandlestickData(ctx, instrumentID, interval, before, after, limit, marketCandles, getCandlesticksEPL) + return ok.GetCandlestickData(ctx, instrumentID, interval, before, after, limit, marketCandles, getCandlestickEPL) } // GetCandlesticksHistory Retrieve history candlestick charts from recent years. @@ -3151,7 +3151,7 @@ func (ok *Okx) GetCandlesticksHistory(ctx context.Context, instrumentID string, // GetIndexCandlesticks Retrieve the candlestick charts of the index. This endpoint can retrieve the latest 1,440 data entries. Charts are returned in groups based on the requested bar. // the response is a list of Candlestick data. func (ok *Okx) GetIndexCandlesticks(ctx context.Context, instrumentID string, interval kline.Interval, before, after time.Time, limit int64) ([]CandleStick, error) { - return ok.GetCandlestickData(ctx, instrumentID, interval, before, after, limit, marketCandlesIndex, getIndexCandlesticksEPL) + return ok.GetCandlestickData(ctx, instrumentID, interval, before, after, limit, marketCandlesIndex, getIndexCandlestickEPL) } // GetMarkPriceCandlesticks Retrieve the candlestick charts of mark price. This endpoint can retrieve the latest 1,440 data entries. Charts are returned in groups based on the requested bar. @@ -3488,7 +3488,7 @@ func (ok *Okx) GetEstimatedDeliveryPrice(ctx context.Context, instrumentID strin return nil, errMissingRequiredParamInstID } params.Set("instId", instrumentID) - return resp, ok.SendHTTPRequest(ctx, exchange.RestSpot, getEstimatedDeliveryPriceEPL, http.MethodGet, common.EncodeURLValues(publicEstimatedPrice, params), nil, &resp, false) + return resp, ok.SendHTTPRequest(ctx, exchange.RestSpot, getEstimatedDeliveryExercisePriceEPL, http.MethodGet, common.EncodeURLValues(publicEstimatedPrice, params), nil, &resp, false) } // GetDiscountRateAndInterestFreeQuota retrieves discount rate level and interest-free quota. diff --git a/exchanges/okx/okx_test.go b/exchanges/okx/okx_test.go index 378120826e3..20b05cd6987 100644 --- a/exchanges/okx/okx_test.go +++ b/exchanges/okx/okx_test.go @@ -989,7 +989,7 @@ func TestCancelAllQuotes(t *testing.T) { switch { case err != nil: t.Error("Okx CancelAllQuotes() error", err) - case err == nil && time.IsZero(): + case time.IsZero(): t.Error("Okx CancelAllQuotes() zero timestamp message ") } } @@ -2237,7 +2237,7 @@ func TestGetOrderHistory(t *testing.T) { _, err := ok.GetOrderHistory(contextGenerate(), &getOrdersRequest) if err == nil { t.Errorf("Okx GetOrderHistory() Expected: %v. received nil", err) - } else if err != nil && !errors.Is(err, errMissingAtLeast1CurrencyPair) { + } else if !errors.Is(err, errMissingAtLeast1CurrencyPair) { t.Errorf("Okx GetOrderHistory() Expected: %v, but found %v", errMissingAtLeast1CurrencyPair, err) } getOrdersRequest.Pairs = []currency.Pair{ diff --git a/exchanges/okx/okx_websocket.go b/exchanges/okx/okx_websocket.go index 29f5435a8d1..3b5a9d2ebe6 100644 --- a/exchanges/okx/okx_websocket.go +++ b/exchanges/okx/okx_websocket.go @@ -1829,9 +1829,6 @@ func (ok *Okx) wsChannelSubscription(operation, channel string, assetType asset. return errIncompleteCurrencyPair } instrumentID = format.Format(pair) - if err != nil { - instrumentID = "" - } } input := &SubscriptionOperationInput{ Operation: operation, @@ -1885,9 +1882,6 @@ func (ok *Okx) wsAuthChannelSubscription(operation, channel string, assetType as return errIncompleteCurrencyPair } instrumentID = format.Format(pair) - if err != nil { - instrumentID = "" - } } if params.Currency { if !pair.IsEmpty() { diff --git a/exchanges/okx/okx_wrapper.go b/exchanges/okx/okx_wrapper.go index d454e30d894..9b1f80d14f6 100644 --- a/exchanges/okx/okx_wrapper.go +++ b/exchanges/okx/okx_wrapper.go @@ -154,7 +154,7 @@ func (ok *Okx) SetDefaults() { } ok.Requester, err = request.New(ok.Name, common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout), - request.WithLimiter(SetRateLimit())) + request.WithLimiter(GetRateLimit())) if err != nil { log.Errorln(log.ExchangeSys, err) } @@ -820,12 +820,8 @@ func (ok *Okx) ModifyOrder(ctx context.Context, action *order.Modify) (*order.Mo if action.Pair.IsEmpty() { return nil, currency.ErrCurrencyPairEmpty } - instrumentID := pairFormat.Format(action.Pair) - if err != nil { - return nil, err - } amendRequest := AmendOrderRequestParams{ - InstrumentID: instrumentID, + InstrumentID: pairFormat.Format(action.Pair), NewQuantity: action.Amount, OrderID: action.OrderID, ClientOrderID: action.ClientOrderID, @@ -888,8 +884,6 @@ func (ok *Okx) CancelBatchOrders(ctx context.Context, o []order.Cancel) (*order. if !ok.SupportsAsset(ord.AssetType) { return nil, fmt.Errorf("%w: %v", asset.ErrNotSupported, ord.AssetType) } - - var instrumentID string var pairFormat currency.PairFormat pairFormat, err = ok.GetPairFormat(ord.AssetType, true) if err != nil { @@ -898,12 +892,8 @@ func (ok *Okx) CancelBatchOrders(ctx context.Context, o []order.Cancel) (*order. if !ord.Pair.IsPopulated() { return nil, errIncompleteCurrencyPair } - instrumentID = pairFormat.Format(ord.Pair) - if err != nil { - return nil, err - } cancelOrderParams[x] = CancelOrderRequestParam{ - InstrumentID: instrumentID, + InstrumentID: pairFormat.Format(ord.Pair), OrderID: ord.OrderID, ClientOrderID: ord.ClientOrderID, } diff --git a/exchanges/okx/ratelimit.go b/exchanges/okx/ratelimit.go index 6ccb0b4f103..bc1fd23d483 100644 --- a/exchanges/okx/ratelimit.go +++ b/exchanges/okx/ratelimit.go @@ -1,12 +1,9 @@ package okx import ( - "context" - "errors" "time" "github.com/thrasher-corp/gocryptotrader/exchanges/request" - "golang.org/x/time/rate" ) // Ratelimit intervals. @@ -18,182 +15,6 @@ const ( tenSecondsInterval = 10 * time.Second ) -// RateLimit implements the request.Limiter interface -type RateLimit struct { - // Trade Endpoints - PlaceOrder *rate.Limiter - PlaceMultipleOrders *rate.Limiter - CancelOrder *rate.Limiter - CancelMultipleOrders *rate.Limiter - AmendOrder *rate.Limiter - AmendMultipleOrders *rate.Limiter - CloseDeposit *rate.Limiter - GetOrderDetails *rate.Limiter - GetOrderList *rate.Limiter - GetOrderHistory7Days *rate.Limiter - GetOrderHistory3Months *rate.Limiter - GetTransactionDetail3Days *rate.Limiter - GetTransactionDetail3Months *rate.Limiter - PlaceAlgoOrder *rate.Limiter - CancelAlgoOrder *rate.Limiter - CancelAdvanceAlgoOrder *rate.Limiter - GetAlgoOrderList *rate.Limiter - GetAlgoOrderHistory *rate.Limiter - GetEasyConvertCurrencyList *rate.Limiter - PlaceEasyConvert *rate.Limiter - GetEasyConvertHistory *rate.Limiter - GetOneClickRepayHistory *rate.Limiter - OneClickRepayCurrencyList *rate.Limiter - TradeOneClickRepay *rate.Limiter - // Block Trading endpoints - GetCounterparties *rate.Limiter - CreateRfq *rate.Limiter - CancelRfq *rate.Limiter - CancelMultipleRfq *rate.Limiter - CancelAllRfqs *rate.Limiter - ExecuteQuote *rate.Limiter - SetQuoteProducts *rate.Limiter - RestMMPStatus *rate.Limiter - CreateQuote *rate.Limiter - CancelQuote *rate.Limiter - CancelMultipleQuotes *rate.Limiter - CancelAllQuotes *rate.Limiter - GetRfqs *rate.Limiter - GetQuotes *rate.Limiter - GetTrades *rate.Limiter - GetTradesHistory *rate.Limiter - GetPublicTrades *rate.Limiter - // Funding - GetCurrencies *rate.Limiter - GetBalance *rate.Limiter - GetAccountAssetValuation *rate.Limiter - FundsTransfer *rate.Limiter - GetFundsTransferState *rate.Limiter - AssetBillsDetails *rate.Limiter - LightningDeposits *rate.Limiter - GetDepositAddress *rate.Limiter - GetDepositHistory *rate.Limiter - Withdrawal *rate.Limiter - LightningWithdrawals *rate.Limiter - CancelWithdrawal *rate.Limiter - GetWithdrawalHistory *rate.Limiter - SmallAssetsConvert *rate.Limiter - // Savings - GetSavingBalance *rate.Limiter - SavingsPurchaseRedempt *rate.Limiter - SetLendingRate *rate.Limiter - GetLendingHistory *rate.Limiter - GetPublicBorrowInfo *rate.Limiter - GetPublicBorrowHistory *rate.Limiter - // Convert - GetConvertCurrencies *rate.Limiter - GetConvertCurrencyPair *rate.Limiter - EstimateQuote *rate.Limiter - ConvertTrade *rate.Limiter - GetConvertHistory *rate.Limiter - // Account - GetAccountBalance *rate.Limiter - GetPositions *rate.Limiter - GetPositionsHistory *rate.Limiter - GetAccountAndPositionRisk *rate.Limiter - GetBillsDetails *rate.Limiter - GetAccountConfiguration *rate.Limiter - SetPositionMode *rate.Limiter - SetLeverage *rate.Limiter - GetMaximumBuyOrSellAmount *rate.Limiter - GetMaximumAvailableTradableAmount *rate.Limiter - IncreaseOrDecreaseMargin *rate.Limiter - GetLeverage *rate.Limiter - GetTheMaximumLoanOfInstrument *rate.Limiter - GetFeeRates *rate.Limiter - GetInterestAccruedData *rate.Limiter - GetInterestRate *rate.Limiter - SetGreeks *rate.Limiter - IsolatedMarginTradingSettings *rate.Limiter - GetMaximumWithdrawals *rate.Limiter - GetAccountRiskState *rate.Limiter - VipLoansBorrowAnsRepay *rate.Limiter - GetBorrowAnsRepayHistoryHistory *rate.Limiter - GetBorrowInterestAndLimit *rate.Limiter - PositionBuilder *rate.Limiter - GetGreeks *rate.Limiter - GetPMLimitation *rate.Limiter - // Sub Account Endpoints - ViewSubaccountList *rate.Limiter - ResetSubAccountAPIKey *rate.Limiter - GetSubaccountTradingBalance *rate.Limiter - GetSubaccountFundingBalance *rate.Limiter - HistoryOfSubaccountTransfer *rate.Limiter - MasterAccountsManageTransfersBetweenSubaccount *rate.Limiter - SetPermissionOfTransferOut *rate.Limiter - GetCustodyTradingSubaccountList *rate.Limiter - GridTrading *rate.Limiter - AmendGridAlgoOrder *rate.Limiter - StopGridAlgoOrder *rate.Limiter - GetGridAlgoOrderList *rate.Limiter - GetGridAlgoOrderHistory *rate.Limiter - GetGridAlgoOrderDetails *rate.Limiter - GetGridAlgoSubOrders *rate.Limiter - GetGridAlgoOrderPositions *rate.Limiter - SpotGridWithdrawIncome *rate.Limiter - ComputeMarginBalance *rate.Limiter - AdjustMarginBalance *rate.Limiter - GetGridAIParameter *rate.Limiter - // Earn - GetOffer *rate.Limiter - Purchase *rate.Limiter - Redeem *rate.Limiter - CancelPurchaseOrRedemption *rate.Limiter - GetEarnActiveOrders *rate.Limiter - GetFundingOrderHistory *rate.Limiter - // Market Data - GetTickers *rate.Limiter - GetIndexTickers *rate.Limiter - GetOrderBook *rate.Limiter - GetCandlesticks *rate.Limiter - GetCandlesticksHistory *rate.Limiter - GetIndexCandlesticks *rate.Limiter - GetMarkPriceCandlesticks *rate.Limiter - GetTradesRequest *rate.Limiter - Get24HTotalVolume *rate.Limiter - GetOracle *rate.Limiter - GetExchangeRateRequest *rate.Limiter - GetIndexComponents *rate.Limiter - GetBlockTickers *rate.Limiter - GetBlockTrades *rate.Limiter - // Public Data Endpoints - GetInstruments *rate.Limiter - GetDeliveryExerciseHistory *rate.Limiter - GetOpenInterest *rate.Limiter - GetFunding *rate.Limiter - GetFundingRateHistory *rate.Limiter - GetLimitPrice *rate.Limiter - GetOptionMarketDate *rate.Limiter - GetEstimatedDeliveryExercisePrice *rate.Limiter - GetDiscountRateAndInterestFreeQuota *rate.Limiter - GetSystemTime *rate.Limiter - GetLiquidationOrders *rate.Limiter - GetMarkPrice *rate.Limiter - GetPositionTiers *rate.Limiter - GetInterestRateAndLoanQuota *rate.Limiter - GetInterestRateAndLoanQuoteForVIPLoans *rate.Limiter - GetUnderlying *rate.Limiter - GetInsuranceFund *rate.Limiter - UnitConvert *rate.Limiter - // Trading Data Endpoints - GetSupportCoin *rate.Limiter - GetTakerVolume *rate.Limiter - GetMarginLendingRatio *rate.Limiter - GetLongShortRatio *rate.Limiter - GetContractsOpenInterestAndVolume *rate.Limiter - GetOptionsOpenInterestAndVolume *rate.Limiter - GetPutCallRatio *rate.Limiter - GetOpenInterestAndVolume *rate.Limiter - GetTakerFlow *rate.Limiter - // Status Endpoints - GetEventStatus *rate.Limiter -} - const ( // Trade Endpoints placeOrderRate = 60 @@ -202,7 +23,7 @@ const ( cancelMultipleOrdersRate = 300 amendOrderRate = 60 amendMultipleOrdersRate = 300 - closeDepositions = 20 + closePositionsRate = 20 getOrderDetails = 60 getOrderListRate = 60 getOrderHistory7DaysRate = 40 @@ -502,7 +323,7 @@ const ( getTickersEPL getIndexTickersEPL getOrderBookEPL - getCandlesticksEPL + getCandlestickEPL getTradesRequestEPL get24HTotalVolumeEPL getOracleEPL @@ -517,7 +338,7 @@ const ( getFundingRateHistoryEPL getLimitPriceEPL getOptionMarketDateEPL - getEstimatedDeliveryPriceEPL + getEstimatedDeliveryExercisePriceEPL getDiscountRateAndInterestFreeQuotaEPL getSystemTimeEPL getLiquidationOrdersEPL @@ -539,519 +360,192 @@ const ( getTakerFlowEPL getEventStatusEPL getCandlestickHistoryEPL - getIndexCandlesticksEPL + getIndexCandlestickEPL ) -// Limit executes rate limiting for Okx exchange given the context and EndpointLimit -func (r *RateLimit) Limit(ctx context.Context, f request.EndpointLimit) error { - switch f { - case placeOrderEPL: - return r.PlaceOrder.Wait(ctx) - case placeMultipleOrdersEPL: - return r.PlaceMultipleOrders.Wait(ctx) - case cancelOrderEPL: - return r.CancelOrder.Wait(ctx) - case cancelMultipleOrdersEPL: - return r.CancelMultipleOrders.Wait(ctx) - case amendOrderEPL: - return r.AmendOrder.Wait(ctx) - case amendMultipleOrdersEPL: - return r.AmendMultipleOrders.Wait(ctx) - case closePositionEPL: - return r.CloseDeposit.Wait(ctx) - case getOrderDetEPL: - return r.GetOrderDetails.Wait(ctx) - case getOrderListEPL: - return r.GetOrderList.Wait(ctx) - case getOrderHistory7DaysEPL: - return r.GetOrderHistory7Days.Wait(ctx) - case getOrderHistory3MonthsEPL: - return r.GetOrderHistory3Months.Wait(ctx) - case getTransactionDetail3DaysEPL: - return r.GetTransactionDetail3Days.Wait(ctx) - case getTransactionDetail3MonthsEPL: - return r.GetTransactionDetail3Months.Wait(ctx) - case placeAlgoOrderEPL: - return r.PlaceAlgoOrder.Wait(ctx) - case cancelAlgoOrderEPL: - return r.CancelAlgoOrder.Wait(ctx) - case cancelAdvanceAlgoOrderEPL: - return r.CancelAdvanceAlgoOrder.Wait(ctx) - case getAlgoOrderListEPL: - return r.GetAlgoOrderList.Wait(ctx) - case getAlgoOrderHistoryEPL: - return r.GetAlgoOrderHistory.Wait(ctx) - case getEasyConvertCurrencyListEPL: - return r.GetEasyConvertCurrencyList.Wait(ctx) - case placeEasyConvertEPL: - return r.PlaceEasyConvert.Wait(ctx) - case getEasyConvertHistoryEPL: - return r.GetEasyConvertHistory.Wait(ctx) - case getOneClickRepayHistoryEPL: - return r.GetOneClickRepayHistory.Wait(ctx) - case oneClickRepayCurrencyListEPL: - return r.OneClickRepayCurrencyList.Wait(ctx) - case tradeOneClickRepayEPL: - return r.TradeOneClickRepay.Wait(ctx) - case getCounterpartiesEPL: - return r.GetCounterparties.Wait(ctx) - case createRfqEPL: - return r.CreateRfq.Wait(ctx) - case cancelRfqEPL: - return r.CancelRfq.Wait(ctx) - case cancelMultipleRfqEPL: - return r.CancelMultipleRfq.Wait(ctx) - case cancelAllRfqsEPL: - return r.CancelAllRfqs.Wait(ctx) - case executeQuoteEPL: - return r.ExecuteQuote.Wait(ctx) - case setQuoteProductsEPL: - return r.SetQuoteProducts.Wait(ctx) - case restMMPStatusEPL: - return r.RestMMPStatus.Wait(ctx) - case createQuoteEPL: - return r.CreateQuote.Wait(ctx) - case cancelQuoteEPL: - return r.CancelQuote.Wait(ctx) - case cancelMultipleQuotesEPL: - return r.CancelMultipleQuotes.Wait(ctx) - case cancelAllQuotesEPL: - return r.CancelAllQuotes.Wait(ctx) - case getRfqsEPL: - return r.GetRfqs.Wait(ctx) - case getQuotesEPL: - return r.GetQuotes.Wait(ctx) - case getTradesEPL: - return r.GetTrades.Wait(ctx) - case getTradesHistoryEPL: - return r.GetTradesHistory.Wait(ctx) - case getPublicTradesEPL: - return r.GetPublicTrades.Wait(ctx) - case getCurrenciesEPL: - return r.GetCurrencies.Wait(ctx) - case getBalanceEPL: - return r.GetBalance.Wait(ctx) - case getAccountAssetValuationEPL: - return r.GetAccountAssetValuation.Wait(ctx) - case fundsTransferEPL: - return r.FundsTransfer.Wait(ctx) - case getFundsTransferStateEPL: - return r.GetFundsTransferState.Wait(ctx) - case assetBillsDetailsEPL: - return r.AssetBillsDetails.Wait(ctx) - case lightningDepositsEPL: - return r.LightningDeposits.Wait(ctx) - case getDepositAddressEPL: - return r.GetDepositAddress.Wait(ctx) - case getDepositHistoryEPL: - return r.GetDepositHistory.Wait(ctx) - case withdrawalEPL: - return r.Withdrawal.Wait(ctx) - case lightningWithdrawalsEPL: - return r.LightningWithdrawals.Wait(ctx) - case cancelWithdrawalEPL: - return r.CancelWithdrawal.Wait(ctx) - case getWithdrawalHistoryEPL: - return r.GetWithdrawalHistory.Wait(ctx) - case smallAssetsConvertEPL: - return r.SmallAssetsConvert.Wait(ctx) - case getSavingBalanceEPL: - return r.GetSavingBalance.Wait(ctx) - case savingsPurchaseRedemptionEPL: - return r.SavingsPurchaseRedempt.Wait(ctx) - case setLendingRateEPL: - return r.SetLendingRate.Wait(ctx) - case getLendingHistoryEPL: - return r.GetLendingHistory.Wait(ctx) - case getPublicBorrowInfoEPL: - return r.GetPublicBorrowInfo.Wait(ctx) - case getPublicBorrowHistoryEPL: - return r.GetPublicBorrowHistory.Wait(ctx) - case getConvertCurrenciesEPL: - return r.GetConvertCurrencies.Wait(ctx) - case getConvertCurrencyPairEPL: - return r.GetConvertCurrencyPair.Wait(ctx) - case estimateQuoteEPL: - return r.EstimateQuote.Wait(ctx) - case convertTradeEPL: - return r.ConvertTrade.Wait(ctx) - case getConvertHistoryEPL: - return r.GetConvertHistory.Wait(ctx) - case getAccountBalanceEPL: - return r.GetAccountBalance.Wait(ctx) - case getPositionsEPL: - return r.GetPositions.Wait(ctx) - case getPositionsHistoryEPL: - return r.GetPositionsHistory.Wait(ctx) - case getAccountAndPositionRiskEPL: - return r.GetAccountAndPositionRisk.Wait(ctx) - case getBillsDetailsEPL: - return r.GetBillsDetails.Wait(ctx) - case getAccountConfigurationEPL: - return r.GetAccountConfiguration.Wait(ctx) - case setPositionModeEPL: - return r.SetPositionMode.Wait(ctx) - case setLeverageEPL: - return r.SetLeverage.Wait(ctx) - case getMaximumBuyOrSellAmountEPL: - return r.GetMaximumBuyOrSellAmount.Wait(ctx) - case getMaximumAvailableTradableAmountEPL: - return r.GetMaximumAvailableTradableAmount.Wait(ctx) - case increaseOrDecreaseMarginEPL: - return r.IncreaseOrDecreaseMargin.Wait(ctx) - case getLeverageEPL: - return r.GetLeverage.Wait(ctx) - case getTheMaximumLoanOfInstrumentEPL: - return r.GetTheMaximumLoanOfInstrument.Wait(ctx) - case getFeeRatesEPL: - return r.GetFeeRates.Wait(ctx) - case getInterestAccruedDataEPL: - return r.GetInterestAccruedData.Wait(ctx) - case getInterestRateEPL: - return r.GetInterestRate.Wait(ctx) - case setGreeksEPL: - return r.SetGreeks.Wait(ctx) - case isolatedMarginTradingSettingsEPL: - return r.IsolatedMarginTradingSettings.Wait(ctx) - case getMaximumWithdrawalsEPL: - return r.GetMaximumWithdrawals.Wait(ctx) - case getAccountRiskStateEPL: - return r.GetAccountRiskState.Wait(ctx) - case vipLoansBorrowAnsRepayEPL: - return r.VipLoansBorrowAnsRepay.Wait(ctx) - case getBorrowAnsRepayHistoryHistoryEPL: - return r.GetBorrowAnsRepayHistoryHistory.Wait(ctx) - case getBorrowInterestAndLimitEPL: - return r.GetBorrowInterestAndLimit.Wait(ctx) - case positionBuilderEPL: - return r.PositionBuilder.Wait(ctx) - case getGreeksEPL: - return r.GetGreeks.Wait(ctx) - case getPMLimitationEPL: - return r.GetPMLimitation.Wait(ctx) - case viewSubaccountListEPL: - return r.ViewSubaccountList.Wait(ctx) - case resetSubAccountAPIKeyEPL: - return r.ResetSubAccountAPIKey.Wait(ctx) - case getSubaccountTradingBalanceEPL: - return r.GetSubaccountTradingBalance.Wait(ctx) - case getSubaccountFundingBalanceEPL: - return r.GetSubaccountFundingBalance.Wait(ctx) - case historyOfSubaccountTransferEPL: - return r.HistoryOfSubaccountTransfer.Wait(ctx) - case masterAccountsManageTransfersBetweenSubaccountEPL: - return r.MasterAccountsManageTransfersBetweenSubaccount.Wait(ctx) - case setPermissionOfTransferOutEPL: - return r.SetPermissionOfTransferOut.Wait(ctx) - case getCustodyTradingSubaccountListEPL: - return r.GetCustodyTradingSubaccountList.Wait(ctx) - case gridTradingEPL: - return r.GridTrading.Wait(ctx) - case amendGridAlgoOrderEPL: - return r.AmendGridAlgoOrder.Wait(ctx) - case stopGridAlgoOrderEPL: - return r.StopGridAlgoOrder.Wait(ctx) - case getGridAlgoOrderListEPL: - return r.GetGridAlgoOrderList.Wait(ctx) - case getGridAlgoOrderHistoryEPL: - return r.GetGridAlgoOrderHistory.Wait(ctx) - case getGridAlgoOrderDetailsEPL: - return r.GetGridAlgoOrderDetails.Wait(ctx) - case getGridAlgoSubOrdersEPL: - return r.GetGridAlgoSubOrders.Wait(ctx) - case getGridAlgoOrderPositionsEPL: - return r.GetGridAlgoOrderPositions.Wait(ctx) - case spotGridWithdrawIncomeEPL: - return r.SpotGridWithdrawIncome.Wait(ctx) - case computeMarginBalanceEPL: - return r.ComputeMarginBalance.Wait(ctx) - case adjustMarginBalanceEPL: - return r.AdjustMarginBalance.Wait(ctx) - case getGridAIParameterEPL: - return r.GetGridAIParameter.Wait(ctx) - case getOfferEPL: - return r.GetOffer.Wait(ctx) - case purchaseEPL: - return r.Purchase.Wait(ctx) - case redeemEPL: - return r.Redeem.Wait(ctx) - case cancelPurchaseOrRedemptionEPL: - return r.CancelPurchaseOrRedemption.Wait(ctx) - case getEarnActiveOrdersEPL: - return r.GetEarnActiveOrders.Wait(ctx) - case getFundingOrderHistoryEPL: - return r.GetFundingOrderHistory.Wait(ctx) - case getTickersEPL: - return r.GetTickers.Wait(ctx) - case getIndexTickersEPL: - return r.GetIndexTickers.Wait(ctx) - case getOrderBookEPL: - return r.GetOrderBook.Wait(ctx) - case getCandlesticksEPL: - return r.GetCandlesticks.Wait(ctx) - case getCandlestickHistoryEPL: - return r.GetCandlesticksHistory.Wait(ctx) - case getIndexCandlesticksEPL: - return r.GetIndexCandlesticks.Wait(ctx) - case getTradesRequestEPL: - return r.GetTradesRequest.Wait(ctx) - case get24HTotalVolumeEPL: - return r.Get24HTotalVolume.Wait(ctx) - case getOracleEPL: - return r.GetOracle.Wait(ctx) - case getExchangeRateRequestEPL: - return r.GetExchangeRateRequest.Wait(ctx) - case getIndexComponentsEPL: - return r.GetIndexComponents.Wait(ctx) - case getBlockTickersEPL: - return r.GetBlockTickers.Wait(ctx) - case getBlockTradesEPL: - return r.GetBlockTrades.Wait(ctx) - case getInstrumentsEPL: - return r.GetInstruments.Wait(ctx) - case getDeliveryExerciseHistoryEPL: - return r.GetDeliveryExerciseHistory.Wait(ctx) - case getOpenInterestEPL: - return r.GetOpenInterest.Wait(ctx) - case getFundingEPL: - return r.GetFunding.Wait(ctx) - case getFundingRateHistoryEPL: - return r.GetFundingRateHistory.Wait(ctx) - case getLimitPriceEPL: - return r.GetLimitPrice.Wait(ctx) - case getOptionMarketDateEPL: - return r.GetOptionMarketDate.Wait(ctx) - case getEstimatedDeliveryPriceEPL: - return r.GetEstimatedDeliveryExercisePrice.Wait(ctx) - case getDiscountRateAndInterestFreeQuotaEPL: - return r.GetDiscountRateAndInterestFreeQuota.Wait(ctx) - case getSystemTimeEPL: - return r.GetSystemTime.Wait(ctx) - case getLiquidationOrdersEPL: - return r.GetLiquidationOrders.Wait(ctx) - case getMarkPriceEPL: - return r.GetMarkPrice.Wait(ctx) - case getPositionTiersEPL: - return r.GetPositionTiers.Wait(ctx) - case getInterestRateAndLoanQuotaEPL: - return r.GetInterestRateAndLoanQuota.Wait(ctx) - case getInterestRateAndLoanQuoteForVIPLoansEPL: - return r.GetInterestRateAndLoanQuoteForVIPLoans.Wait(ctx) - case getUnderlyingEPL: - return r.GetUnderlying.Wait(ctx) - case getInsuranceFundEPL: - return r.GetInsuranceFund.Wait(ctx) - case unitConvertEPL: - return r.UnitConvert.Wait(ctx) - case getSupportCoinEPL: - return r.GetSupportCoin.Wait(ctx) - case getTakerVolumeEPL: - return r.GetTakerVolume.Wait(ctx) - case getMarginLendingRatioEPL: - return r.GetMarginLendingRatio.Wait(ctx) - case getLongShortRatioEPL: - return r.GetLongShortRatio.Wait(ctx) - case getContractsOpenInterestAndVolumeEPL: - return r.GetContractsOpenInterestAndVolume.Wait(ctx) - case getOptionsOpenInterestAndVolumeEPL: - return r.GetOptionsOpenInterestAndVolume.Wait(ctx) - case getPutCallRatioEPL: - return r.GetPutCallRatio.Wait(ctx) - case getOpenInterestAndVolumeEPL: - return r.GetOpenInterestAndVolume.Wait(ctx) - case getTakerFlowEPL: - return r.GetTakerFlow.Wait(ctx) - case getEventStatusEPL: - return r.GetEventStatus.Wait(ctx) - default: - return errors.New("endpoint rate limit functionality not found") - } -} - -// SetRateLimit returns a RateLimit instance, which implements the request.Limiter interface. -func SetRateLimit() *RateLimit { - return &RateLimit{ +// GetRateLimit returns a RateLimit instance, which implements the request.Limiter interface. +func GetRateLimit() request.RateLimitDefinitions { + return request.RateLimitDefinitions{ // Trade Endpoints - PlaceOrder: request.NewRateLimit(twoSecondsInterval, placeOrderRate), - PlaceMultipleOrders: request.NewRateLimit(twoSecondsInterval, placeMultipleOrdersRate), - CancelOrder: request.NewRateLimit(twoSecondsInterval, cancelOrderRate), - CancelMultipleOrders: request.NewRateLimit(twoSecondsInterval, cancelMultipleOrdersRate), - AmendOrder: request.NewRateLimit(twoSecondsInterval, amendOrderRate), - AmendMultipleOrders: request.NewRateLimit(twoSecondsInterval, amendMultipleOrdersRate), - CloseDeposit: request.NewRateLimit(twoSecondsInterval, closeDepositions), - GetOrderDetails: request.NewRateLimit(twoSecondsInterval, getOrderDetails), - GetOrderList: request.NewRateLimit(twoSecondsInterval, getOrderListRate), - GetOrderHistory7Days: request.NewRateLimit(twoSecondsInterval, getOrderHistory7DaysRate), - GetOrderHistory3Months: request.NewRateLimit(twoSecondsInterval, getOrderHistory3MonthsRate), - GetTransactionDetail3Days: request.NewRateLimit(twoSecondsInterval, getTransactionDetail3DaysRate), - GetTransactionDetail3Months: request.NewRateLimit(twoSecondsInterval, getTransactionDetail3MonthsRate), - PlaceAlgoOrder: request.NewRateLimit(twoSecondsInterval, placeAlgoOrderRate), - CancelAlgoOrder: request.NewRateLimit(twoSecondsInterval, cancelAlgoOrderRate), - CancelAdvanceAlgoOrder: request.NewRateLimit(twoSecondsInterval, cancelAdvanceAlgoOrderRate), - GetAlgoOrderList: request.NewRateLimit(twoSecondsInterval, getAlgoOrderListRate), - GetAlgoOrderHistory: request.NewRateLimit(twoSecondsInterval, getAlgoOrderHistoryRate), - GetEasyConvertCurrencyList: request.NewRateLimit(twoSecondsInterval, getEasyConvertCurrencyListRate), - PlaceEasyConvert: request.NewRateLimit(twoSecondsInterval, placeEasyConvert), - GetEasyConvertHistory: request.NewRateLimit(twoSecondsInterval, getEasyConvertHistory), - GetOneClickRepayHistory: request.NewRateLimit(twoSecondsInterval, getOneClickRepayHistory), - OneClickRepayCurrencyList: request.NewRateLimit(twoSecondsInterval, oneClickRepayCurrencyList), - TradeOneClickRepay: request.NewRateLimit(twoSecondsInterval, tradeOneClickRepay), + placeOrderEPL: request.NewRateLimitWithWeight(twoSecondsInterval, placeOrderRate, 1), + placeMultipleOrdersEPL: request.NewRateLimitWithWeight(twoSecondsInterval, placeMultipleOrdersRate, 1), + cancelOrderEPL: request.NewRateLimitWithWeight(twoSecondsInterval, cancelOrderRate, 1), + cancelMultipleOrdersEPL: request.NewRateLimitWithWeight(twoSecondsInterval, cancelMultipleOrdersRate, 1), + amendOrderEPL: request.NewRateLimitWithWeight(twoSecondsInterval, amendOrderRate, 1), + amendMultipleOrdersEPL: request.NewRateLimitWithWeight(twoSecondsInterval, amendMultipleOrdersRate, 1), + closePositionEPL: request.NewRateLimitWithWeight(twoSecondsInterval, closePositionsRate, 1), + getOrderDetEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getOrderDetails, 1), + getOrderListEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getOrderListRate, 1), + getOrderHistory7DaysEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getOrderHistory7DaysRate, 1), + getOrderHistory3MonthsEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getOrderHistory3MonthsRate, 1), + getTransactionDetail3DaysEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getTransactionDetail3DaysRate, 1), + getTransactionDetail3MonthsEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getTransactionDetail3MonthsRate, 1), + placeAlgoOrderEPL: request.NewRateLimitWithWeight(twoSecondsInterval, placeAlgoOrderRate, 1), + cancelAlgoOrderEPL: request.NewRateLimitWithWeight(twoSecondsInterval, cancelAlgoOrderRate, 1), + cancelAdvanceAlgoOrderEPL: request.NewRateLimitWithWeight(twoSecondsInterval, cancelAdvanceAlgoOrderRate, 1), + getAlgoOrderListEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getAlgoOrderListRate, 1), + getAlgoOrderHistoryEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getAlgoOrderHistoryRate, 1), + getEasyConvertCurrencyListEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getEasyConvertCurrencyListRate, 1), + placeEasyConvertEPL: request.NewRateLimitWithWeight(twoSecondsInterval, placeEasyConvert, 1), + getEasyConvertHistoryEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getEasyConvertHistory, 1), + getOneClickRepayHistoryEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getOneClickRepayHistory, 1), + oneClickRepayCurrencyListEPL: request.NewRateLimitWithWeight(twoSecondsInterval, oneClickRepayCurrencyList, 1), + tradeOneClickRepayEPL: request.NewRateLimitWithWeight(twoSecondsInterval, tradeOneClickRepay, 1), // Block Trading endpoints - GetCounterparties: request.NewRateLimit(twoSecondsInterval, getCounterpartiesRate), - CreateRfq: request.NewRateLimit(twoSecondsInterval, createRfqRate), - CancelRfq: request.NewRateLimit(twoSecondsInterval, cancelRfqRate), - CancelMultipleRfq: request.NewRateLimit(twoSecondsInterval, cancelMultipleRfqRate), - CancelAllRfqs: request.NewRateLimit(twoSecondsInterval, cancelAllRfqsRate), - ExecuteQuote: request.NewRateLimit(threeSecondsInterval, executeQuoteRate), - SetQuoteProducts: request.NewRateLimit(twoSecondsInterval, setQuoteProducts), - RestMMPStatus: request.NewRateLimit(twoSecondsInterval, restMMPStatus), - CreateQuote: request.NewRateLimit(twoSecondsInterval, createQuoteRate), - CancelQuote: request.NewRateLimit(twoSecondsInterval, cancelQuoteRate), - CancelMultipleQuotes: request.NewRateLimit(twoSecondsInterval, cancelMultipleQuotesRate), - CancelAllQuotes: request.NewRateLimit(twoSecondsInterval, cancelAllQuotes), - GetRfqs: request.NewRateLimit(twoSecondsInterval, getRfqsRate), - GetQuotes: request.NewRateLimit(twoSecondsInterval, getQuotesRate), - GetTrades: request.NewRateLimit(twoSecondsInterval, getTradesRate), - GetTradesHistory: request.NewRateLimit(twoSecondsInterval, getTradesHistoryRate), - GetPublicTrades: request.NewRateLimit(twoSecondsInterval, getPublicTradesRate), + getCounterpartiesEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getCounterpartiesRate, 1), + createRfqEPL: request.NewRateLimitWithWeight(twoSecondsInterval, createRfqRate, 1), + cancelRfqEPL: request.NewRateLimitWithWeight(twoSecondsInterval, cancelRfqRate, 1), + cancelMultipleRfqEPL: request.NewRateLimitWithWeight(twoSecondsInterval, cancelMultipleRfqRate, 1), + cancelAllRfqsEPL: request.NewRateLimitWithWeight(twoSecondsInterval, cancelAllRfqsRate, 1), + executeQuoteEPL: request.NewRateLimitWithWeight(threeSecondsInterval, executeQuoteRate, 1), + setQuoteProductsEPL: request.NewRateLimitWithWeight(twoSecondsInterval, setQuoteProducts, 1), + restMMPStatusEPL: request.NewRateLimitWithWeight(twoSecondsInterval, restMMPStatus, 1), + createQuoteEPL: request.NewRateLimitWithWeight(twoSecondsInterval, createQuoteRate, 1), + cancelQuoteEPL: request.NewRateLimitWithWeight(twoSecondsInterval, cancelQuoteRate, 1), + cancelMultipleQuotesEPL: request.NewRateLimitWithWeight(twoSecondsInterval, cancelMultipleQuotesRate, 1), + cancelAllQuotesEPL: request.NewRateLimitWithWeight(twoSecondsInterval, cancelAllQuotes, 1), + getRfqsEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getRfqsRate, 1), + getQuotesEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getQuotesRate, 1), + getTradesEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getTradesRate, 1), + getTradesHistoryEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getTradesHistoryRate, 1), + getPublicTradesEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getPublicTradesRate, 1), // Funding - GetCurrencies: request.NewRateLimit(oneSecondInterval, getCurrenciesRate), - GetBalance: request.NewRateLimit(oneSecondInterval, getBalanceRate), - GetAccountAssetValuation: request.NewRateLimit(twoSecondsInterval, getAccountAssetValuationRate), - FundsTransfer: request.NewRateLimit(oneSecondInterval, fundsTransferRate), - GetFundsTransferState: request.NewRateLimit(oneSecondInterval, getFundsTransferStateRate), - AssetBillsDetails: request.NewRateLimit(oneSecondInterval, assetBillsDetailsRate), - LightningDeposits: request.NewRateLimit(oneSecondInterval, lightningDepositsRate), - GetDepositAddress: request.NewRateLimit(oneSecondInterval, getDepositAddressRate), - GetDepositHistory: request.NewRateLimit(oneSecondInterval, getDepositHistoryRate), - Withdrawal: request.NewRateLimit(oneSecondInterval, withdrawalRate), - LightningWithdrawals: request.NewRateLimit(oneSecondInterval, lightningWithdrawalsRate), - CancelWithdrawal: request.NewRateLimit(oneSecondInterval, cancelWithdrawalRate), - GetWithdrawalHistory: request.NewRateLimit(oneSecondInterval, getWithdrawalHistoryRate), - SmallAssetsConvert: request.NewRateLimit(oneSecondInterval, smallAssetsConvertRate), - GetSavingBalance: request.NewRateLimit(oneSecondInterval, getSavingBalanceRate), - SavingsPurchaseRedempt: request.NewRateLimit(oneSecondInterval, savingsPurchaseRedemptionRate), - SetLendingRate: request.NewRateLimit(oneSecondInterval, setLendingRateRate), - GetLendingHistory: request.NewRateLimit(oneSecondInterval, getLendingHistoryRate), - GetPublicBorrowInfo: request.NewRateLimit(oneSecondInterval, getPublicBorrowInfoRate), - GetPublicBorrowHistory: request.NewRateLimit(oneSecondInterval, getPublicBorrowHistoryRate), + getCurrenciesEPL: request.NewRateLimitWithWeight(oneSecondInterval, getCurrenciesRate, 1), + getBalanceEPL: request.NewRateLimitWithWeight(oneSecondInterval, getBalanceRate, 1), + getAccountAssetValuationEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getAccountAssetValuationRate, 1), + fundsTransferEPL: request.NewRateLimitWithWeight(oneSecondInterval, fundsTransferRate, 1), + getFundsTransferStateEPL: request.NewRateLimitWithWeight(oneSecondInterval, getFundsTransferStateRate, 1), + assetBillsDetailsEPL: request.NewRateLimitWithWeight(oneSecondInterval, assetBillsDetailsRate, 1), + lightningDepositsEPL: request.NewRateLimitWithWeight(oneSecondInterval, lightningDepositsRate, 1), + getDepositAddressEPL: request.NewRateLimitWithWeight(oneSecondInterval, getDepositAddressRate, 1), + getDepositHistoryEPL: request.NewRateLimitWithWeight(oneSecondInterval, getDepositHistoryRate, 1), + withdrawalEPL: request.NewRateLimitWithWeight(oneSecondInterval, withdrawalRate, 1), + lightningWithdrawalsEPL: request.NewRateLimitWithWeight(oneSecondInterval, lightningWithdrawalsRate, 1), + cancelWithdrawalEPL: request.NewRateLimitWithWeight(oneSecondInterval, cancelWithdrawalRate, 1), + getWithdrawalHistoryEPL: request.NewRateLimitWithWeight(oneSecondInterval, getWithdrawalHistoryRate, 1), + smallAssetsConvertEPL: request.NewRateLimitWithWeight(oneSecondInterval, smallAssetsConvertRate, 1), + getSavingBalanceEPL: request.NewRateLimitWithWeight(oneSecondInterval, getSavingBalanceRate, 1), + savingsPurchaseRedemptionEPL: request.NewRateLimitWithWeight(oneSecondInterval, savingsPurchaseRedemptionRate, 1), + setLendingRateEPL: request.NewRateLimitWithWeight(oneSecondInterval, setLendingRateRate, 1), + getLendingHistoryEPL: request.NewRateLimitWithWeight(oneSecondInterval, getLendingHistoryRate, 1), + getPublicBorrowInfoEPL: request.NewRateLimitWithWeight(oneSecondInterval, getPublicBorrowInfoRate, 1), + getPublicBorrowHistoryEPL: request.NewRateLimitWithWeight(oneSecondInterval, getPublicBorrowHistoryRate, 1), // Convert - GetConvertCurrencies: request.NewRateLimit(oneSecondInterval, getConvertCurrenciesRate), - GetConvertCurrencyPair: request.NewRateLimit(oneSecondInterval, getConvertCurrencyPairRate), - EstimateQuote: request.NewRateLimit(oneSecondInterval, estimateQuoteRate), - ConvertTrade: request.NewRateLimit(oneSecondInterval, convertTradeRate), - GetConvertHistory: request.NewRateLimit(oneSecondInterval, getConvertHistoryRate), + getConvertCurrenciesEPL: request.NewRateLimitWithWeight(oneSecondInterval, getConvertCurrenciesRate, 1), + getConvertCurrencyPairEPL: request.NewRateLimitWithWeight(oneSecondInterval, getConvertCurrencyPairRate, 1), + estimateQuoteEPL: request.NewRateLimitWithWeight(oneSecondInterval, estimateQuoteRate, 1), + convertTradeEPL: request.NewRateLimitWithWeight(oneSecondInterval, convertTradeRate, 1), + getConvertHistoryEPL: request.NewRateLimitWithWeight(oneSecondInterval, getConvertHistoryRate, 1), // Account - GetAccountBalance: request.NewRateLimit(twoSecondsInterval, getAccountBalanceRate), - GetPositions: request.NewRateLimit(twoSecondsInterval, getPositionsRate), - GetPositionsHistory: request.NewRateLimit(tenSecondsInterval, getPositionsHistoryRate), - GetAccountAndPositionRisk: request.NewRateLimit(twoSecondsInterval, getAccountAndPositionRiskRate), - GetBillsDetails: request.NewRateLimit(oneSecondInterval, getBillsDetailsRate), - GetAccountConfiguration: request.NewRateLimit(twoSecondsInterval, getAccountConfigurationRate), - SetPositionMode: request.NewRateLimit(twoSecondsInterval, setPositionModeRate), - SetLeverage: request.NewRateLimit(twoSecondsInterval, setLeverageRate), - GetMaximumBuyOrSellAmount: request.NewRateLimit(twoSecondsInterval, getMaximumBuyOrSellAmountRate), - GetMaximumAvailableTradableAmount: request.NewRateLimit(twoSecondsInterval, getMaximumAvailableTradableAmountRate), - IncreaseOrDecreaseMargin: request.NewRateLimit(twoSecondsInterval, increaseOrDecreaseMarginRate), - GetLeverage: request.NewRateLimit(twoSecondsInterval, getLeverageRate), - GetTheMaximumLoanOfInstrument: request.NewRateLimit(twoSecondsInterval, getTheMaximumLoanOfInstrumentRate), - GetFeeRates: request.NewRateLimit(twoSecondsInterval, getFeeRatesRate), - GetInterestAccruedData: request.NewRateLimit(twoSecondsInterval, getInterestAccruedDataRate), - GetInterestRate: request.NewRateLimit(twoSecondsInterval, getInterestRateRate), - SetGreeks: request.NewRateLimit(twoSecondsInterval, setGreeksRate), - IsolatedMarginTradingSettings: request.NewRateLimit(twoSecondsInterval, isolatedMarginTradingSettingsRate), - GetMaximumWithdrawals: request.NewRateLimit(twoSecondsInterval, getMaximumWithdrawalsRate), - GetAccountRiskState: request.NewRateLimit(twoSecondsInterval, getAccountRiskStateRate), - VipLoansBorrowAnsRepay: request.NewRateLimit(oneSecondInterval, vipLoansBorrowAndRepayRate), - GetBorrowAnsRepayHistoryHistory: request.NewRateLimit(twoSecondsInterval, getBorrowAnsRepayHistoryHistoryRate), - GetBorrowInterestAndLimit: request.NewRateLimit(twoSecondsInterval, getBorrowInterestAndLimitRate), - PositionBuilder: request.NewRateLimit(twoSecondsInterval, positionBuilderRate), - GetGreeks: request.NewRateLimit(twoSecondsInterval, getGreeksRate), - GetPMLimitation: request.NewRateLimit(twoSecondsInterval, getPMLimitation), + getAccountBalanceEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getAccountBalanceRate, 1), + getPositionsEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getPositionsRate, 1), + getPositionsHistoryEPL: request.NewRateLimitWithWeight(tenSecondsInterval, getPositionsHistoryRate, 1), + getAccountAndPositionRiskEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getAccountAndPositionRiskRate, 1), + getBillsDetailsEPL: request.NewRateLimitWithWeight(oneSecondInterval, getBillsDetailsRate, 1), + getAccountConfigurationEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getAccountConfigurationRate, 1), + setPositionModeEPL: request.NewRateLimitWithWeight(twoSecondsInterval, setPositionModeRate, 1), + setLeverageEPL: request.NewRateLimitWithWeight(twoSecondsInterval, setLeverageRate, 1), + getMaximumBuyOrSellAmountEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getMaximumBuyOrSellAmountRate, 1), + getMaximumAvailableTradableAmountEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getMaximumAvailableTradableAmountRate, 1), + increaseOrDecreaseMarginEPL: request.NewRateLimitWithWeight(twoSecondsInterval, increaseOrDecreaseMarginRate, 1), + getLeverageEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getLeverageRate, 1), + getTheMaximumLoanOfInstrumentEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getTheMaximumLoanOfInstrumentRate, 1), + getFeeRatesEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getFeeRatesRate, 1), + getInterestAccruedDataEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getInterestAccruedDataRate, 1), + getInterestRateEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getInterestRateRate, 1), + setGreeksEPL: request.NewRateLimitWithWeight(twoSecondsInterval, setGreeksRate, 1), + isolatedMarginTradingSettingsEPL: request.NewRateLimitWithWeight(twoSecondsInterval, isolatedMarginTradingSettingsRate, 1), + getMaximumWithdrawalsEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getMaximumWithdrawalsRate, 1), + getAccountRiskStateEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getAccountRiskStateRate, 1), + vipLoansBorrowAnsRepayEPL: request.NewRateLimitWithWeight(oneSecondInterval, vipLoansBorrowAndRepayRate, 1), + getBorrowAnsRepayHistoryHistoryEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getBorrowAnsRepayHistoryHistoryRate, 1), + getBorrowInterestAndLimitEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getBorrowInterestAndLimitRate, 1), + positionBuilderEPL: request.NewRateLimitWithWeight(twoSecondsInterval, positionBuilderRate, 1), + getGreeksEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getGreeksRate, 1), + getPMLimitationEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getPMLimitation, 1), // Sub Account Endpoints - ViewSubaccountList: request.NewRateLimit(twoSecondsInterval, viewSubaccountListRate), - ResetSubAccountAPIKey: request.NewRateLimit(oneSecondInterval, resetSubAccountAPIKey), - GetSubaccountTradingBalance: request.NewRateLimit(twoSecondsInterval, getSubaccountTradingBalanceRate), - GetSubaccountFundingBalance: request.NewRateLimit(twoSecondsInterval, getSubaccountFundingBalanceRate), - HistoryOfSubaccountTransfer: request.NewRateLimit(oneSecondInterval, historyOfSubaccountTransferRate), - MasterAccountsManageTransfersBetweenSubaccount: request.NewRateLimit(oneSecondInterval, masterAccountsManageTransfersBetweenSubaccountRate), - SetPermissionOfTransferOut: request.NewRateLimit(oneSecondInterval, setPermissionOfTransferOutRate), - GetCustodyTradingSubaccountList: request.NewRateLimit(oneSecondInterval, getCustodyTradingSubaccountListRate), + viewSubaccountListEPL: request.NewRateLimitWithWeight(twoSecondsInterval, viewSubaccountListRate, 1), + resetSubAccountAPIKeyEPL: request.NewRateLimitWithWeight(oneSecondInterval, resetSubAccountAPIKey, 1), + getSubaccountTradingBalanceEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getSubaccountTradingBalanceRate, 1), + getSubaccountFundingBalanceEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getSubaccountFundingBalanceRate, 1), + historyOfSubaccountTransferEPL: request.NewRateLimitWithWeight(oneSecondInterval, historyOfSubaccountTransferRate, 1), + masterAccountsManageTransfersBetweenSubaccountEPL: request.NewRateLimitWithWeight(oneSecondInterval, masterAccountsManageTransfersBetweenSubaccountRate, 1), + setPermissionOfTransferOutEPL: request.NewRateLimitWithWeight(oneSecondInterval, setPermissionOfTransferOutRate, 1), + getCustodyTradingSubaccountListEPL: request.NewRateLimitWithWeight(oneSecondInterval, getCustodyTradingSubaccountListRate, 1), // Grid Trading Endpoints - GridTrading: request.NewRateLimit(twoSecondsInterval, gridTradingRate), - AmendGridAlgoOrder: request.NewRateLimit(twoSecondsInterval, amendGridAlgoOrderRate), - StopGridAlgoOrder: request.NewRateLimit(twoSecondsInterval, stopGridAlgoOrderRate), - GetGridAlgoOrderList: request.NewRateLimit(twoSecondsInterval, getGridAlgoOrderListRate), - GetGridAlgoOrderHistory: request.NewRateLimit(twoSecondsInterval, getGridAlgoOrderHistoryRate), - GetGridAlgoOrderDetails: request.NewRateLimit(twoSecondsInterval, getGridAlgoOrderDetailsRate), - GetGridAlgoSubOrders: request.NewRateLimit(twoSecondsInterval, getGridAlgoSubOrdersRate), - GetGridAlgoOrderPositions: request.NewRateLimit(twoSecondsInterval, getGridAlgoOrderPositionsRate), - SpotGridWithdrawIncome: request.NewRateLimit(twoSecondsInterval, spotGridWithdrawIncomeRate), - ComputeMarginBalance: request.NewRateLimit(twoSecondsInterval, computeMarginBalance), - AdjustMarginBalance: request.NewRateLimit(twoSecondsInterval, adjustMarginBalance), - GetGridAIParameter: request.NewRateLimit(twoSecondsInterval, getGridAIParameter), + gridTradingEPL: request.NewRateLimitWithWeight(twoSecondsInterval, gridTradingRate, 1), + amendGridAlgoOrderEPL: request.NewRateLimitWithWeight(twoSecondsInterval, amendGridAlgoOrderRate, 1), + stopGridAlgoOrderEPL: request.NewRateLimitWithWeight(twoSecondsInterval, stopGridAlgoOrderRate, 1), + getGridAlgoOrderListEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getGridAlgoOrderListRate, 1), + getGridAlgoOrderHistoryEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getGridAlgoOrderHistoryRate, 1), + getGridAlgoOrderDetailsEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getGridAlgoOrderDetailsRate, 1), + getGridAlgoSubOrdersEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getGridAlgoSubOrdersRate, 1), + getGridAlgoOrderPositionsEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getGridAlgoOrderPositionsRate, 1), + spotGridWithdrawIncomeEPL: request.NewRateLimitWithWeight(twoSecondsInterval, spotGridWithdrawIncomeRate, 1), + computeMarginBalanceEPL: request.NewRateLimitWithWeight(twoSecondsInterval, computeMarginBalance, 1), + adjustMarginBalanceEPL: request.NewRateLimitWithWeight(twoSecondsInterval, adjustMarginBalance, 1), + getGridAIParameterEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getGridAIParameter, 1), // Earn - GetOffer: request.NewRateLimit(oneSecondInterval, getOffer), - Purchase: request.NewRateLimit(oneSecondInterval, purchase), - Redeem: request.NewRateLimit(oneSecondInterval, redeem), - CancelPurchaseOrRedemption: request.NewRateLimit(oneSecondInterval, cancelPurchaseOrRedemption), - GetEarnActiveOrders: request.NewRateLimit(oneSecondInterval, getEarnActiveOrders), - GetFundingOrderHistory: request.NewRateLimit(oneSecondInterval, getFundingOrderHistory), + getOfferEPL: request.NewRateLimitWithWeight(oneSecondInterval, getOffer, 1), + purchaseEPL: request.NewRateLimitWithWeight(oneSecondInterval, purchase, 1), + redeemEPL: request.NewRateLimitWithWeight(oneSecondInterval, redeem, 1), + cancelPurchaseOrRedemptionEPL: request.NewRateLimitWithWeight(oneSecondInterval, cancelPurchaseOrRedemption, 1), + getEarnActiveOrdersEPL: request.NewRateLimitWithWeight(oneSecondInterval, getEarnActiveOrders, 1), + getFundingOrderHistoryEPL: request.NewRateLimitWithWeight(oneSecondInterval, getFundingOrderHistory, 1), // Market Data - GetTickers: request.NewRateLimit(twoSecondsInterval, getTickersRate), - GetIndexTickers: request.NewRateLimit(twoSecondsInterval, getIndexTickersRate), - GetOrderBook: request.NewRateLimit(twoSecondsInterval, getOrderBookRate), - GetCandlesticks: request.NewRateLimit(twoSecondsInterval, getCandlesticksRate), - GetCandlesticksHistory: request.NewRateLimit(twoSecondsInterval, getCandlesticksHistoryRate), - GetIndexCandlesticks: request.NewRateLimit(twoSecondsInterval, getIndexCandlesticksRate), - GetMarkPriceCandlesticks: request.NewRateLimit(twoSecondsInterval, getMarkPriceCandlesticksRate), - GetTradesRequest: request.NewRateLimit(twoSecondsInterval, getTradesRequestRate), - Get24HTotalVolume: request.NewRateLimit(twoSecondsInterval, get24HTotalVolumeRate), - GetOracle: request.NewRateLimit(fiveSecondsInterval, getOracleRate), - GetExchangeRateRequest: request.NewRateLimit(twoSecondsInterval, getExchangeRateRequestRate), - GetIndexComponents: request.NewRateLimit(twoSecondsInterval, getIndexComponentsRate), - GetBlockTickers: request.NewRateLimit(twoSecondsInterval, getBlockTickersRate), - GetBlockTrades: request.NewRateLimit(twoSecondsInterval, getBlockTradesRate), + getTickersEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getTickersRate, 1), + getIndexTickersEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getIndexTickersRate, 1), + getOrderBookEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getOrderBookRate, 1), + getCandlestickEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getCandlesticksRate, 1), + getCandlestickHistoryEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getCandlesticksHistoryRate, 1), + getIndexCandlestickEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getIndexCandlesticksRate, 1), + getTradesRequestEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getTradesRequestRate, 1), + get24HTotalVolumeEPL: request.NewRateLimitWithWeight(twoSecondsInterval, get24HTotalVolumeRate, 1), + getOracleEPL: request.NewRateLimitWithWeight(fiveSecondsInterval, getOracleRate, 1), + getExchangeRateRequestEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getExchangeRateRequestRate, 1), + getIndexComponentsEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getIndexComponentsRate, 1), + getBlockTickersEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getBlockTickersRate, 1), + getBlockTradesEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getBlockTradesRate, 1), // Public Data Endpoints - GetInstruments: request.NewRateLimit(twoSecondsInterval, getInstrumentsRate), - GetDeliveryExerciseHistory: request.NewRateLimit(twoSecondsInterval, getDeliveryExerciseHistoryRate), - GetOpenInterest: request.NewRateLimit(twoSecondsInterval, getOpenInterestRate), - GetFunding: request.NewRateLimit(twoSecondsInterval, getFundingRate), - GetFundingRateHistory: request.NewRateLimit(twoSecondsInterval, getFundingRateHistoryRate), - GetLimitPrice: request.NewRateLimit(twoSecondsInterval, getLimitPriceRate), - GetOptionMarketDate: request.NewRateLimit(twoSecondsInterval, getOptionMarketDateRate), - GetEstimatedDeliveryExercisePrice: request.NewRateLimit(twoSecondsInterval, getEstimatedDeliveryExercisePriceRate), - GetDiscountRateAndInterestFreeQuota: request.NewRateLimit(twoSecondsInterval, getDiscountRateAndInterestFreeQuotaRate), - GetSystemTime: request.NewRateLimit(twoSecondsInterval, getSystemTimeRate), - GetLiquidationOrders: request.NewRateLimit(twoSecondsInterval, getLiquidationOrdersRate), - GetMarkPrice: request.NewRateLimit(twoSecondsInterval, getMarkPriceRate), - GetPositionTiers: request.NewRateLimit(twoSecondsInterval, getPositionTiersRate), - GetInterestRateAndLoanQuota: request.NewRateLimit(twoSecondsInterval, getInterestRateAndLoanQuotaRate), - GetInterestRateAndLoanQuoteForVIPLoans: request.NewRateLimit(twoSecondsInterval, getInterestRateAndLoanQuoteForVIPLoansRate), - GetUnderlying: request.NewRateLimit(twoSecondsInterval, getUnderlyingRate), - GetInsuranceFund: request.NewRateLimit(twoSecondsInterval, getInsuranceFundRate), - UnitConvert: request.NewRateLimit(twoSecondsInterval, unitConvertRate), + getInstrumentsEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getInstrumentsRate, 1), + getDeliveryExerciseHistoryEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getDeliveryExerciseHistoryRate, 1), + getOpenInterestEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getOpenInterestRate, 1), + getFundingEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getFundingRate, 1), + getFundingRateHistoryEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getFundingRateHistoryRate, 1), + getLimitPriceEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getLimitPriceRate, 1), + getOptionMarketDateEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getOptionMarketDateRate, 1), + getEstimatedDeliveryExercisePriceEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getEstimatedDeliveryExercisePriceRate, 1), + getDiscountRateAndInterestFreeQuotaEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getDiscountRateAndInterestFreeQuotaRate, 1), + getSystemTimeEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getSystemTimeRate, 1), + getLiquidationOrdersEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getLiquidationOrdersRate, 1), + getMarkPriceEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getMarkPriceRate, 1), + getPositionTiersEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getPositionTiersRate, 1), + getInterestRateAndLoanQuotaEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getInterestRateAndLoanQuotaRate, 1), + getInterestRateAndLoanQuoteForVIPLoansEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getInterestRateAndLoanQuoteForVIPLoansRate, 1), + getUnderlyingEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getUnderlyingRate, 1), + getInsuranceFundEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getInsuranceFundRate, 1), + unitConvertEPL: request.NewRateLimitWithWeight(twoSecondsInterval, unitConvertRate, 1), // Trading Data Endpoints - GetSupportCoin: request.NewRateLimit(twoSecondsInterval, getSupportCoinRate), - GetTakerVolume: request.NewRateLimit(twoSecondsInterval, getTakerVolumeRate), - GetMarginLendingRatio: request.NewRateLimit(twoSecondsInterval, getMarginLendingRatioRate), - GetLongShortRatio: request.NewRateLimit(twoSecondsInterval, getLongShortRatioRate), - GetContractsOpenInterestAndVolume: request.NewRateLimit(twoSecondsInterval, getContractsOpenInterestAndVolumeRate), - GetOptionsOpenInterestAndVolume: request.NewRateLimit(twoSecondsInterval, getOptionsOpenInterestAndVolumeRate), - GetPutCallRatio: request.NewRateLimit(twoSecondsInterval, getPutCallRatioRate), - GetOpenInterestAndVolume: request.NewRateLimit(twoSecondsInterval, getOpenInterestAndVolumeRate), - GetTakerFlow: request.NewRateLimit(twoSecondsInterval, getTakerFlowRate), + getSupportCoinEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getSupportCoinRate, 1), + getTakerVolumeEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getTakerVolumeRate, 1), + getMarginLendingRatioEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getMarginLendingRatioRate, 1), + getLongShortRatioEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getLongShortRatioRate, 1), + getContractsOpenInterestAndVolumeEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getContractsOpenInterestAndVolumeRate, 1), + getOptionsOpenInterestAndVolumeEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getOptionsOpenInterestAndVolumeRate, 1), + getPutCallRatioEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getPutCallRatioRate, 1), + getOpenInterestAndVolumeEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getOpenInterestAndVolumeRate, 1), + getTakerFlowEPL: request.NewRateLimitWithWeight(twoSecondsInterval, getTakerFlowRate, 1), // Status Endpoints - GetEventStatus: request.NewRateLimit(fiveSecondsInterval, getEventStatusRate), + getEventStatusEPL: request.NewRateLimitWithWeight(fiveSecondsInterval, getEventStatusRate, 1), } } diff --git a/exchanges/poloniex/poloniex.go b/exchanges/poloniex/poloniex.go index 4ef6ff0b79d..4f95e53dff8 100644 --- a/exchanges/poloniex/poloniex.go +++ b/exchanges/poloniex/poloniex.go @@ -934,7 +934,7 @@ func (p *Poloniex) SendHTTPRequest(ctx context.Context, ep exchange.URL, path st HTTPRecording: p.HTTPRecording, } - return p.SendPayload(ctx, request.Unset, func() (*request.Item, error) { + return p.SendPayload(ctx, request.UnAuth, func() (*request.Item, error) { return item, nil }, request.UnauthenticatedRequest) } @@ -950,7 +950,7 @@ func (p *Poloniex) SendAuthenticatedHTTPRequest(ctx context.Context, ep exchange return err } - return p.SendPayload(ctx, request.Unset, func() (*request.Item, error) { + return p.SendPayload(ctx, request.Auth, func() (*request.Item, error) { headers := make(map[string]string) headers["Content-Type"] = "application/x-www-form-urlencoded" headers["Key"] = creds.Key diff --git a/exchanges/poloniex/poloniex_wrapper.go b/exchanges/poloniex/poloniex_wrapper.go index 5fe3526a0ed..5118ae63163 100644 --- a/exchanges/poloniex/poloniex_wrapper.go +++ b/exchanges/poloniex/poloniex_wrapper.go @@ -123,7 +123,7 @@ func (p *Poloniex) SetDefaults() { p.Requester, err = request.New(p.Name, common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout), - request.WithLimiter(SetRateLimit())) + request.WithLimiter(GetRateLimit())) if err != nil { log.Errorln(log.ExchangeSys, err) } diff --git a/exchanges/poloniex/ratelimit.go b/exchanges/poloniex/ratelimit.go index 69c2d243f98..71c2ec66609 100644 --- a/exchanges/poloniex/ratelimit.go +++ b/exchanges/poloniex/ratelimit.go @@ -1,11 +1,9 @@ package poloniex import ( - "context" "time" "github.com/thrasher-corp/gocryptotrader/exchanges/request" - "golang.org/x/time/rate" ) const ( @@ -14,28 +12,14 @@ const ( poloniexUnauthRate = 6 ) -// RateLimit implements the request.Limiter interface -type RateLimit struct { - Auth *rate.Limiter - UnAuth *rate.Limiter -} - -// Limit limits outbound calls -func (r *RateLimit) Limit(ctx context.Context, f request.EndpointLimit) error { - if f == request.Auth { - return r.Auth.Wait(ctx) - } - return r.UnAuth.Wait(ctx) -} - -// SetRateLimit returns the rate limit for the exchange +// GetRateLimit returns the rate limit for the exchange // If your account's volume is over $5 million in 30 day volume, // you may be eligible for an API rate limit increase. // Please email poloniex@circle.com. // As per https://docs.poloniex.com/#http-api -func SetRateLimit() *RateLimit { - return &RateLimit{ - Auth: request.NewRateLimit(poloniexRateInterval, poloniexAuthRate), - UnAuth: request.NewRateLimit(poloniexRateInterval, poloniexUnauthRate), +func GetRateLimit() request.RateLimitDefinitions { + return request.RateLimitDefinitions{ + request.Auth: request.NewRateLimitWithWeight(poloniexRateInterval, poloniexAuthRate, 1), + request.UnAuth: request.NewRateLimitWithWeight(poloniexRateInterval, poloniexUnauthRate, 1), } } diff --git a/exchanges/request/limit.go b/exchanges/request/limit.go index e2179399fbc..cc0f193f27a 100644 --- a/exchanges/request/limit.go +++ b/exchanges/request/limit.go @@ -14,6 +14,10 @@ import ( var ( ErrRateLimiterAlreadyDisabled = errors.New("rate limiter already disabled") ErrRateLimiterAlreadyEnabled = errors.New("rate limiter already enabled") + + errLimiterSystemIsNil = errors.New("limiter system is nil") + errInvalidWeightCount = errors.New("invalid weight count must equal or greater than 1") + errSpecificRateLimiterIsNil = errors.New("specific rate limiter is nil") ) // Const here define individual functionality sub types for rate limiting @@ -23,26 +27,35 @@ const ( UnAuth ) -// BasicLimit denotes basic rate limit that implements the Limiter interface -// does not need to set endpoint functionality. -type BasicLimit struct { - r *rate.Limiter -} - -// Limit executes a single rate limit set by NewRateLimit -func (b *BasicLimit) Limit(ctx context.Context, _ EndpointLimit) error { - return b.r.Wait(ctx) -} - // EndpointLimit defines individual endpoint rate limits that are set when // New is called. -type EndpointLimit int +type EndpointLimit uint16 + +// Weight defines the number of reservations to be used. This is a generalised +// weight for rate limiting. e.g. n weight = n request. i.e. 50 Weight = 50 +// requests. +type Weight uint8 + +// RateLimitDefinitions is a map of endpoint limits to rate limiters +type RateLimitDefinitions map[interface{}]*RateLimiterWithWeight + +// RateLimiterWithWeight is a rate limiter coupled with a weight count which +// refers to the number or weighting of the request. This is used to define +// the rate limit for a specific endpoint. +type RateLimiterWithWeight struct { + *rate.Limiter + Weight +} -// Limiter interface groups rate limit functionality defined in the REST -// wrapper for extended rate limiting configuration i.e. Shells of rate -// limits with a global rate for sub rates. -type Limiter interface { - Limit(context.Context, EndpointLimit) error +// Reservations is a slice of rate reservations +type Reservations []*rate.Reservation + +// CancelAll cancels all potential reservations to free up rate limiter for +// context cancellations and deadline exceeded cases. +func (r Reservations) CancelAll() { + for x := range r { + r[x].Cancel() + } } // NewRateLimit creates a new RateLimit based of time interval and how many @@ -59,10 +72,25 @@ func NewRateLimit(interval time.Duration, actions int) *rate.Limiter { return rate.NewLimiter(rate.Limit(rps), 1) } +// NewRateLimitWithWeight creates a new RateLimit based of time interval and how +// many actions allowed. This also has a weight count which refers to the number +// or weighting of the request. This is used to define the rate limit for a +// specific endpoint. +func NewRateLimitWithWeight(interval time.Duration, actions int, weight Weight) *RateLimiterWithWeight { + return GetRateLimiterWithWeight(NewRateLimit(interval, actions), weight) +} + +// GetRateLimiterWithWeight couples a rate limiter with a weight count into an +// accepted defined rate limiter with weight struct +func GetRateLimiterWithWeight(l *rate.Limiter, weight Weight) *RateLimiterWithWeight { + return &RateLimiterWithWeight{l, weight} +} + // NewBasicRateLimit returns an object that implements the limiter interface // for basic rate limit -func NewBasicRateLimit(interval time.Duration, actions int) Limiter { - return &BasicLimit{NewRateLimit(interval, actions)} +func NewBasicRateLimit(interval time.Duration, actions int, weight Weight) RateLimitDefinitions { + rl := NewRateLimitWithWeight(interval, actions, weight) + return RateLimitDefinitions{Unset: rl, Auth: rl, UnAuth: rl} } // InitiateRateLimit sleeps for designated end point rate limits @@ -73,12 +101,46 @@ func (r *Requester) InitiateRateLimit(ctx context.Context, e EndpointLimit) erro if atomic.LoadInt32(&r.disableRateLimiter) == 1 { return nil } + if r.limiter == nil { + return fmt.Errorf("cannot rate limit request %w", errLimiterSystemIsNil) + } + + rateLimiter := r.limiter[e] - if r.limiter != nil { - return r.limiter.Limit(ctx, e) + if rateLimiter == nil { + return fmt.Errorf("cannot rate limit request %w for endpoint %d", errSpecificRateLimiterIsNil, e) } - return nil + if rateLimiter.Weight <= 0 { + return fmt.Errorf("cannot rate limit request %w for endpoint %d", errInvalidWeightCount, e) + } + + var finalDelay time.Duration + var reservations = make(Reservations, rateLimiter.Weight) + for i := Weight(0); i < rateLimiter.Weight; i++ { + // Consume 1 weight at a time as this avoids needing burst capacity in the limiter, + // which would otherwise allow the rate limit to be exceeded over short periods + reservations[i] = rateLimiter.Reserve() + finalDelay = reservations[i].Delay() + } + + if dl, ok := ctx.Deadline(); ok && dl.Before(time.Now().Add(finalDelay)) { + reservations.CancelAll() + return fmt.Errorf("rate limit delay of %s will exceed deadline: %w", + finalDelay, + context.DeadlineExceeded) + } + + tick := time.NewTimer(finalDelay) + select { + case <-tick.C: + return nil + case <-ctx.Done(): + tick.Stop() + reservations.CancelAll() + return ctx.Err() + } + // TODO: Shutdown case } // DisableRateLimiter disables the rate limiting system for the exchange diff --git a/exchanges/request/options.go b/exchanges/request/options.go index 51f1673505a..29ce0612a9f 100644 --- a/exchanges/request/options.go +++ b/exchanges/request/options.go @@ -8,9 +8,9 @@ func WithBackoff(b Backoff) RequesterOption { } // WithLimiter configures the rate limiter for a Requester. -func WithLimiter(l Limiter) RequesterOption { +func WithLimiter(def RateLimitDefinitions) RequesterOption { return func(r *Requester) { - r.limiter = l + r.limiter = def } } diff --git a/exchanges/request/request.go b/exchanges/request/request.go index 6b07d583eae..9c0b5c86e9b 100644 --- a/exchanges/request/request.go +++ b/exchanges/request/request.go @@ -149,10 +149,12 @@ func (r *Requester) doRequest(ctx context.Context, endpoint EndpointLimit, newRe default: } - // Initiate a rate limit reservation and sleep on requested endpoint - err := r.InitiateRateLimit(ctx, endpoint) - if err != nil { - return fmt.Errorf("failed to rate limit HTTP request: %w", err) + if r.limiter != nil { + // Initiate a rate limit reservation and sleep on requested endpoint + err := r.InitiateRateLimit(ctx, endpoint) + if err != nil { + return fmt.Errorf("failed to rate limit HTTP request: %w", err) + } } p, err := newRequest() @@ -231,7 +233,15 @@ func (r *Requester) doRequest(ctx context.Context, endpoint EndpointLimit, newRe log.Errorf(log.RequestSys, "%s request has failed. Retrying request in %s, attempt %d", r.name, delay, attempt) } - time.Sleep(delay) + if delay > 0 { + // Allow for context cancellation while delaying the retry. + select { + case <-time.After(delay): + case <-ctx.Done(): + return ctx.Err() + } + } + continue } diff --git a/exchanges/request/request_test.go b/exchanges/request/request_test.go index 4d0398b498d..c083b989ec9 100644 --- a/exchanges/request/request_test.go +++ b/exchanges/request/request_test.go @@ -3,7 +3,6 @@ package request import ( "context" "errors" - "fmt" "io" "log" "math" @@ -28,12 +27,12 @@ import ( const unexpected = "unexpected values" var testURL string -var serverLimit *rate.Limiter +var serverLimit *RateLimiterWithWeight func TestMain(m *testing.M) { serverLimitInterval := time.Millisecond * 500 - serverLimit = NewRateLimit(serverLimitInterval, 1) - serverLimitRetry := NewRateLimit(serverLimitInterval, 1) + serverLimit = NewRateLimitWithWeight(serverLimitInterval, 1, 1) + serverLimitRetry := NewRateLimitWithWeight(serverLimitInterval, 1, 1) sm := http.NewServeMux() sm.HandleFunc("/", func(w http.ResponseWriter, _ *http.Request) { w.Header().Set("Content-Type", "application/json") @@ -102,26 +101,26 @@ func TestMain(m *testing.M) { os.Exit(issues) } -func TestNewRateLimit(t *testing.T) { +func TestNewRateLimitWithWeight(t *testing.T) { t.Parallel() - r := NewRateLimit(time.Second*10, 5) + r := NewRateLimitWithWeight(time.Second*10, 5, 1) if r.Limit() != 0.5 { t.Fatal(unexpected) } // Ensures rate limiting factor is the same - r = NewRateLimit(time.Second*2, 1) + r = NewRateLimitWithWeight(time.Second*2, 1, 1) if r.Limit() != 0.5 { t.Fatal(unexpected) } // Test for open rate limit - r = NewRateLimit(time.Second*2, 0) + r = NewRateLimitWithWeight(time.Second*2, 0, 1) if r.Limit() != rate.Inf { t.Fatal(unexpected) } - r = NewRateLimit(0, 69) + r = NewRateLimitWithWeight(0, 69, 1) if r.Limit() != rate.Inf { t.Fatal(unexpected) } @@ -201,39 +200,13 @@ func TestCheckRequest(t *testing.T) { } } -type GlobalLimitTest struct { - Auth *rate.Limiter - UnAuth *rate.Limiter -} - -var errEndpointLimitNotFound = errors.New("endpoint limit not found") - -func (g *GlobalLimitTest) Limit(ctx context.Context, e EndpointLimit) error { - switch e { - case Auth: - if g.Auth == nil { - return errors.New("auth rate not set") - } - return g.Auth.Wait(ctx) - case UnAuth: - if g.UnAuth == nil { - return errors.New("unauth rate not set") - } - return g.UnAuth.Wait(ctx) - default: - return fmt.Errorf("cannot execute functionality: %d %w", - e, - errEndpointLimitNotFound) - } -} - -var globalshell = GlobalLimitTest{ - Auth: NewRateLimit(time.Millisecond*600, 1), - UnAuth: NewRateLimit(time.Second*1, 100)} +var globalshell = RateLimitDefinitions{ + Auth: NewRateLimitWithWeight(time.Millisecond*600, 1, 1), + UnAuth: NewRateLimitWithWeight(time.Second*1, 100, 1)} func TestDoRequest(t *testing.T) { t.Parallel() - r, err := New("test", new(http.Client), WithLimiter(&globalshell)) + r, err := New("test", new(http.Client), WithLimiter(globalshell)) if err != nil { t.Fatal(err) } @@ -270,13 +243,9 @@ func TestDoRequest(t *testing.T) { } // Invalid/missing endpoint limit - err = r.SendPayload(ctx, Unset, func() (*Item, error) { - return &Item{ - Path: testURL, - }, nil - }, UnauthenticatedRequest) - if !errors.Is(err, errEndpointLimitNotFound) { - t.Fatalf("expected: %v but received: %v", errEndpointLimitNotFound, err) + err = r.SendPayload(ctx, Unset, func() (*Item, error) { return &Item{Path: testURL}, nil }, UnauthenticatedRequest) + if !errors.Is(err, errSpecificRateLimiterIsNil) { + t.Fatalf("expected: %v but received: %v", errSpecificRateLimiterIsNil, err) } // Force debug @@ -497,7 +466,7 @@ func TestDoRequest_NotRetryable(t *testing.T) { func TestGetNonce(t *testing.T) { t.Parallel() - r, err := New("test", new(http.Client), WithLimiter(&globalshell)) + r, err := New("test", new(http.Client), WithLimiter(globalshell)) require.NoError(t, err) n1 := r.GetNonce(nonce.Unix) assert.NotZero(t, n1) @@ -505,7 +474,7 @@ func TestGetNonce(t *testing.T) { assert.NotZero(t, n2) assert.NotEqual(t, n1, n2) - r2, err := New("test", new(http.Client), WithLimiter(&globalshell)) + r2, err := New("test", new(http.Client), WithLimiter(globalshell)) require.NoError(t, err) n3 := r2.GetNonce(nonce.UnixNano) assert.NotZero(t, n3) @@ -520,7 +489,7 @@ func TestGetNonce(t *testing.T) { // 40532461 30.29 ns/op 0 B/op 0 allocs/op (prev) // 45329203 26.53 ns/op 0 B/op 0 allocs/op func BenchmarkGetNonce(b *testing.B) { - r, err := New("test", new(http.Client), WithLimiter(&globalshell)) + r, err := New("test", new(http.Client), WithLimiter(globalshell)) require.NoError(b, err) b.ResetTimer() for i := 0; i < b.N; i++ { @@ -536,9 +505,7 @@ func TestSetProxy(t *testing.T) { if !errors.Is(err, ErrRequestSystemIsNil) { t.Fatalf("received: '%v', but expected: '%v'", err, ErrRequestSystemIsNil) } - r, err = New("test", - &http.Client{Transport: new(http.Transport)}, - WithLimiter(&globalshell)) + r, err = New("test", &http.Client{Transport: new(http.Transport)}, WithLimiter(globalshell)) if err != nil { t.Fatal(err) } @@ -561,16 +528,11 @@ func TestSetProxy(t *testing.T) { } func TestBasicLimiter(t *testing.T) { - r, err := New("test", - new(http.Client), - WithLimiter(NewBasicRateLimit(time.Second, 1))) + r, err := New("test", new(http.Client), WithLimiter(NewBasicRateLimit(time.Second, 1, 1))) if err != nil { t.Fatal(err) } - i := Item{ - Path: "http://www.google.com", - Method: http.MethodGet, - } + i := Item{Path: "http://www.google.com", Method: http.MethodGet} ctx := context.Background() tn := time.Now() @@ -595,9 +557,7 @@ func TestBasicLimiter(t *testing.T) { } func TestEnableDisableRateLimit(t *testing.T) { - r, err := New("TestRequest", - new(http.Client), - WithLimiter(NewBasicRateLimit(time.Minute, 1))) + r, err := New("TestRequest", new(http.Client), WithLimiter(NewBasicRateLimit(time.Minute, 1, 1))) if err != nil { t.Fatal(err) } diff --git a/exchanges/request/request_types.go b/exchanges/request/request_types.go index 8213bdbbb2f..bf1c258f5f1 100644 --- a/exchanges/request/request_types.go +++ b/exchanges/request/request_types.go @@ -27,7 +27,7 @@ var ( // Requester struct for the request client type Requester struct { _HTTPClient *client - limiter Limiter + limiter RateLimitDefinitions reporter Reporter name string userAgent string diff --git a/exchanges/yobit/yobit_wrapper.go b/exchanges/yobit/yobit_wrapper.go index 00b9a341749..58447754ec2 100644 --- a/exchanges/yobit/yobit_wrapper.go +++ b/exchanges/yobit/yobit_wrapper.go @@ -77,7 +77,7 @@ func (y *Yobit) SetDefaults() { y.Requester, err = request.New(y.Name, common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout), // Server responses are cached every 2 seconds. - request.WithLimiter(request.NewBasicRateLimit(time.Second, 1))) + request.WithLimiter(request.NewBasicRateLimit(time.Second, 1, 1))) if err != nil { log.Errorln(log.ExchangeSys, err) } From abba66ffd5823c21544cc100a5e306ebf70db76c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Jun 2024 16:18:52 +1000 Subject: [PATCH 2/7] build(deps): Bump bufbuild/buf-setup-action from 1.32.1 to 1.32.2 (#1559) Bumps [bufbuild/buf-setup-action](https://github.com/bufbuild/buf-setup-action) from 1.32.1 to 1.32.2. - [Release notes](https://github.com/bufbuild/buf-setup-action/releases) - [Commits](https://github.com/bufbuild/buf-setup-action/compare/v1.32.1...v1.32.2) --- updated-dependencies: - dependency-name: bufbuild/buf-setup-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/proto-lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/proto-lint.yml b/.github/workflows/proto-lint.yml index e6f335bce36..f3e36c5c165 100644 --- a/.github/workflows/proto-lint.yml +++ b/.github/workflows/proto-lint.yml @@ -21,7 +21,7 @@ jobs: go install google.golang.org/protobuf/cmd/protoc-gen-go@latest go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest - - uses: bufbuild/buf-setup-action@v1.32.1 + - uses: bufbuild/buf-setup-action@v1.32.2 - name: buf generate working-directory: ./gctrpc From afb6f75d8845f1de093ca5a7ba90f45962b1e3c9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Jun 2024 16:34:06 +1000 Subject: [PATCH 3/7] build(deps): Bump github.com/spf13/viper from 1.18.2 to 1.19.0 (#1560) Bumps [github.com/spf13/viper](https://github.com/spf13/viper) from 1.18.2 to 1.19.0. - [Release notes](https://github.com/spf13/viper/releases) - [Commits](https://github.com/spf13/viper/compare/v1.18.2...v1.19.0) --- updated-dependencies: - dependency-name: github.com/spf13/viper dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 4 ++-- go.sum | 11 ++++++----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index eb59d2ab4cf..502f9ed384f 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/pkg/errors v0.9.1 github.com/pquerna/otp v1.4.0 github.com/shopspring/decimal v1.4.0 - github.com/spf13/viper v1.18.2 + github.com/spf13/viper v1.19.0 github.com/stretchr/testify v1.9.0 github.com/thrasher-corp/gct-ta v0.0.0-20200623072738-f2b55b7f9f41 github.com/thrasher-corp/goose v2.7.0-rc4.0.20191002032028-0f2c2a27abdb+incompatible @@ -41,7 +41,7 @@ require ( github.com/hashicorp/hcl v1.0.0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect - github.com/pelletier/go-toml/v2 v2.1.0 // indirect + github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect diff --git a/go.sum b/go.sum index 8f09dd8842c..0a9f5450ae0 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.110.10 h1:LXy9GEO+timppncPIAZoOj3l58LIU9k+kn48AN7IO3Y= +cloud.google.com/go v0.112.1 h1:uJSeirPke5UNZHIb4SxfZklVSiWWVqW4oXlETwZziwM= cloud.google.com/go/compute v1.25.1 h1:ZRpHJedLtTpKgr3RV1Fx23NuaAEN1Zfx9hw1u4aJdjU= cloud.google.com/go/compute v1.25.1/go.mod h1:oopOIR53ly6viBYxaDhBfJwzUAxf1zE//uf3IB011ls= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= @@ -135,8 +135,8 @@ github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo= -github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= -github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -190,12 +190,13 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= -github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= -github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= +github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= +github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= From 1199f38546d68f1a4b5e84e6743850b094c2e31d Mon Sep 17 00:00:00 2001 From: Gareth Kirwan Date: Fri, 7 Jun 2024 08:54:08 +0700 Subject: [PATCH 4/7] subscriptions: Encapsulate, replace Pair with Pairs and refactor; improve exchange support * Websocket: Use ErrSubscribedAlready instead of errChannelAlreadySubscribed * Subscriptions: Replace Pair with Pairs Given that some subscriptions have multiple pairs, support that as the standard. * Docs: Update subscriptions in add new exch * RPC: Update Subscription Pairs * Linter: Disable testifylint.Len We deliberately use Equal over Len to avoid spamming the contents of large Slices * Websocket: Add suffix to state consts * Binance: Subscription Pairs support * Bitfinex: Subscription Pairs support * Bithumb: Subscription Pairs support * Bitmex: Subscription Pairs support * Bitstamp: Subscription Pairs support * BTCMarkets: Subscription Pairs support * BTSE: Subscription Pairs support * Coinbase: Subscription Pairs support * Coinut: Subscription Pairs support * GateIO: Subscription Pairs support * Gemini: Subscription Pairs support and improvement * Hitbtc: Subscription Pairs support * Huboi: Subscription Pairs support * Kucoin: Subscription Pairs support * Okcoin: Subscription Pairs support * Poloniex: Subscription Pairs support * Kraken: Add subscription Pairs support Note: This is a naieve implementation because we want to rebase the kraken websocket rewrite on top of this * Bybit: Subscription Pairs support * Okx: Subscription Pairs support * Bitmex: Subsription configuration * Fixes unauthenticated websocket left as CanUseAuth * Fixes auth subs happening privately * CoinbasePro: Subscription Configuration * Consolidate ProductIDs when all subscriptions are for the same list * Websocket: Log actual sent message when Verbose * Subscriptions: Improve clarity of which key is which in Match * Subscriptions: Lint fix for HugeParam * Subscriptions: Add AddPairs and move keys from test * Subscriptions: Simplify subscription keys and add key types * Subscriptions: Add List.GroupPairs Rename sub.AddPairs * Subscription: Fix ExactKey not matching 0 pairs * Subscriptions: Remove unused IdentityKey and HasPairKey * Subscriptions: Fix GetKey test * Subscriptions: Test coverage improvements * Websocket: Change State on Add/Remove * Subscriptions: Improve error context * Subscriptions: Fix Enable: false subs not ignored * Bitfinex: Fix WsAuth test failing on DataHandler DataHandler is eaten by dataMonitor now, so we need to use ToRoutine * Deribit: Subscription Pairs support * Websocket: Accept nil lists for checkSubscriptions If the user passes in a nil (implicitly empty) list, we would not panic. Therefore the burden of correctness about that data lies with them. The list of subscriptions is empty, and that's okay, and possibly convenient * Websocket: Add context to NilPointer errors * Subscriptions: Add context to nil errors * Exchange: Fix error expectations in UnsubToWSChans --- .golangci.yml | 2 + config/config_types.go | 6 +- currency/pairs.go | 39 +- currency/pairs_test.go | 9 + docs/ADD_NEW_EXCHANGE.md | 46 +- engine/rpcserver.go | 2 +- exchanges/asset/asset.go | 15 +- exchanges/binance/binance_test.go | 14 +- exchanges/binance/binance_websocket.go | 38 +- exchanges/binance/binance_wrapper.go | 4 +- exchanges/binanceus/binanceus_websocket.go | 18 +- exchanges/bitfinex/bitfinex_test.go | 132 +- exchanges/bitfinex/bitfinex_websocket.go | 140 +- exchanges/bitfinex/bitfinex_wrapper.go | 4 +- exchanges/bithumb/bithumb_websocket.go | 61 +- exchanges/bithumb/bithumb_websocket_test.go | 4 +- exchanges/bithumb/bithumb_wrapper.go | 2 +- exchanges/bitmex/bitmex_websocket.go | 178 +- exchanges/bitmex/bitmex_wrapper.go | 16 +- exchanges/bitstamp/bitstamp_websocket.go | 36 +- exchanges/btcmarkets/btcmarkets.go | 14 +- exchanges/btcmarkets/btcmarkets_websocket.go | 143 +- exchanges/btse/btse_websocket.go | 28 +- exchanges/bybit/bybit_inverse_websocket.go | 15 +- exchanges/bybit/bybit_linear_websocket.go | 14 +- exchanges/bybit/bybit_options_websocket.go | 15 +- exchanges/bybit/bybit_websocket.go | 28 +- exchanges/coinbasepro/coinbasepro_test.go | 15 +- exchanges/coinbasepro/coinbasepro_types.go | 18 +- .../coinbasepro/coinbasepro_websocket.go | 176 +- exchanges/coinbasepro/coinbasepro_wrapper.go | 10 +- exchanges/coinut/coinut_websocket.go | 64 +- exchanges/deribit/deribit_websocket.go | 107 +- exchanges/exchange.go | 14 +- exchanges/exchange_test.go | 32 +- exchanges/exchange_types.go | 2 +- exchanges/gateio/gateio_websocket.go | 30 +- .../gateio/gateio_ws_delivery_futures.go | 36 +- exchanges/gateio/gateio_ws_futures.go | 31 +- exchanges/gateio/gateio_ws_option.go | 32 +- exchanges/gemini/gemini_types.go | 9 +- exchanges/gemini/gemini_websocket.go | 109 +- exchanges/hitbtc/hitbtc_types.go | 19 +- exchanges/hitbtc/hitbtc_websocket.go | 122 +- exchanges/huobi/huobi_websocket.go | 64 +- exchanges/interfaces.go | 6 +- exchanges/kraken/kraken_test.go | 4 +- exchanges/kraken/kraken_types.go | 10 +- exchanges/kraken/kraken_websocket.go | 65 +- exchanges/kucoin/kucoin_test.go | 91 +- exchanges/kucoin/kucoin_websocket.go | 90 +- exchanges/kucoin/kucoin_wrapper.go | 4 +- exchanges/okcoin/okcoin_websocket.go | 68 +- exchanges/okx/okx_websocket.go | 120 +- exchanges/poloniex/poloniex_types.go | 20 +- exchanges/poloniex/poloniex_websocket.go | 117 +- exchanges/sharedtestvalues/customex.go | 6 +- .../sharedtestvalues/sharedtestvalues.go | 16 +- exchanges/stream/websocket.go | 289 +- exchanges/stream/websocket_connection.go | 27 +- exchanges/stream/websocket_test.go | 428 +- exchanges/stream/websocket_types.go | 40 +- exchanges/subscription/keys.go | 88 + exchanges/subscription/keys_test.go | 110 + exchanges/subscription/list.go | 32 + exchanges/subscription/list_test.go | 47 + exchanges/subscription/store.go | 208 + exchanges/subscription/store_test.go | 182 + exchanges/subscription/subscription.go | 195 +- exchanges/subscription/subscription_test.go | 122 +- gctrpc/rpc.pb.go | 4022 ++++++++--------- gctrpc/rpc.proto | 2 +- gctrpc/rpc.swagger.json | 2 +- internal/testing/exchange/exchange.go | 19 +- .../testing/subscriptions/subscriptions.go | 29 + 75 files changed, 4516 insertions(+), 3856 deletions(-) create mode 100644 exchanges/subscription/keys.go create mode 100644 exchanges/subscription/keys_test.go create mode 100644 exchanges/subscription/list.go create mode 100644 exchanges/subscription/list_test.go create mode 100644 exchanges/subscription/store.go create mode 100644 exchanges/subscription/store_test.go create mode 100644 internal/testing/subscriptions/subscriptions.go diff --git a/.golangci.yml b/.golangci.yml index 585c5ca0f9b..b5324f959b5 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -146,6 +146,8 @@ linters-settings: disable: - require-error - float-compare + # We deliberately use Equal over Len to avoid spamming the contents of large Slices + - len issues: max-issues-per-linter: 0 diff --git a/config/config_types.go b/config/config_types.go index ab662f14ddb..bc52fb6e156 100644 --- a/config/config_types.go +++ b/config/config_types.go @@ -317,9 +317,9 @@ type FeaturesEnabledConfig struct { // FeaturesConfig stores the exchanges supported and enabled features type FeaturesConfig struct { - Supports FeaturesSupportedConfig `json:"supports"` - Enabled FeaturesEnabledConfig `json:"enabled"` - Subscriptions []*subscription.Subscription `json:"subscriptions,omitempty"` + Supports FeaturesSupportedConfig `json:"supports"` + Enabled FeaturesEnabledConfig `json:"enabled"` + Subscriptions subscription.List `json:"subscriptions,omitempty"` } // APIEndpointsConfig stores the API endpoint addresses diff --git a/currency/pairs.go b/currency/pairs.go index 12c2d9b6c57..cd4d55c9b97 100644 --- a/currency/pairs.go +++ b/currency/pairs.go @@ -109,8 +109,7 @@ func (p Pairs) Lower() Pairs { return newSlice } -// Contains checks to see if a specified pair exists inside a currency pair -// array +// Contains checks to see if a specified pair exists inside a currency pair array func (p Pairs) Contains(check Pair, exact bool) bool { for i := range p { if (exact && p[i].Equal(check)) || @@ -121,15 +120,13 @@ func (p Pairs) Contains(check Pair, exact bool) bool { return false } -// ContainsAll checks to see if all pairs supplied are contained within the -// original pairs list. +// ContainsAll checks to see if all pairs supplied are contained within the original pairs list func (p Pairs) ContainsAll(check Pairs, exact bool) error { if len(check) == 0 { return ErrCurrencyPairsEmpty } - comparative := make(Pairs, len(p)) - copy(comparative, p) + comparative := slices.Clone(p) list: for x := range check { for y := range comparative { @@ -204,8 +201,7 @@ func (p Pairs) GetPairsByCurrencies(currencies Currencies) Pairs { // Remove removes the specified pair from the list of pairs if it exists func (p Pairs) Remove(pair Pair) (Pairs, error) { - pairs := make(Pairs, len(p)) - copy(pairs, p) + pairs := slices.Clone(p) for x := range p { if p[x].Equal(pair) { return append(pairs[:x], pairs[x+1:]...), nil @@ -481,3 +477,30 @@ func (p Pairs) GetPairsByBase(baseTerm Code) (Pairs, error) { } return pairs, nil } + +// equalKey is a small key for testing pair equality without delimiter +type equalKey struct { + Base *Item + Quote *Item +} + +// Equal checks to see if two lists of pairs contain only the same pairs, ignoring delimiter and case +// Does not check for inverted/reciprocal pairs +func (p Pairs) Equal(b Pairs) bool { + if len(p) != len(b) { + return false + } + if len(p) == 0 { + return true + } + m := map[equalKey]struct{}{} + for i := range p { + m[equalKey{Base: p[i].Base.Item, Quote: p[i].Quote.Item}] = struct{}{} + } + for i := range b { + if _, ok := m[equalKey{Base: b[i].Base.Item, Quote: b[i].Quote.Item}]; !ok { + return false + } + } + return true +} diff --git a/currency/pairs_test.go b/currency/pairs_test.go index 7c4ca9b414a..ee769ea4c2e 100644 --- a/currency/pairs_test.go +++ b/currency/pairs_test.go @@ -870,3 +870,12 @@ func TestGetPairsByBase(t *testing.T) { t.Fatalf("received: '%v' but expected '%v'", len(got), 3) } } + +// TestPairsEqual exercises Pairs.Equal +func TestPairsEqual(t *testing.T) { + t.Parallel() + orig := Pairs{NewPairWithDelimiter("USDT", "BTC", "-"), NewPair(DAI, XRP), NewPair(DAI, BTC)} + assert.True(t, orig.Equal(Pairs{NewPair(DAI, XRP), NewPair(DAI, BTC), NewPair(USDT, BTC)}), "Equal Pairs should return true") + assert.Equal(t, "USDT-BTC", orig[0].String(), "Equal Pairs should not effect original order or format") + assert.False(t, orig.Equal(Pairs{NewPair(DAI, XRP), NewPair(DAI, BTC), NewPair(USD, LTC)}), "UnEqual Pairs should return false") +} diff --git a/docs/ADD_NEW_EXCHANGE.md b/docs/ADD_NEW_EXCHANGE.md index 47b5db793b1..d0d4d511391 100644 --- a/docs/ADD_NEW_EXCHANGE.md +++ b/docs/ADD_NEW_EXCHANGE.md @@ -721,7 +721,7 @@ func (f *FTX) WsConnect() error { } } // Generates the default subscription set, based off enabled pairs. - subs, err := f.GenerateDefaultSubscriptions() + subs, err := f.generateSubscriptions() if err != nil { return err } @@ -733,10 +733,10 @@ func (f *FTX) WsConnect() error { - Create function to generate default subscriptions: ```go -// GenerateDefaultSubscriptions generates default subscription -func (f *FTX) GenerateDefaultSubscriptions() ([]subscription.Subscription, error) { - var subscriptions []subscription.Subscription - subscriptions = append(subscriptions, subscription.Subscription{ +// generateSubscriptions generates default subscription +func (f *FTX) generateSubscriptions() (subscription.List, error) { + var subscriptions subscription.List + subscriptions = append(subscriptions, &subscription.Subscription{ Channel: wsMarkets, }) // Ranges over available channels, pairs and asset types to produce a full @@ -754,9 +754,9 @@ func (f *FTX) GenerateDefaultSubscriptions() ([]subscription.Subscription, error "-") for x := range channels { subscriptions = append(subscriptions, - subscription.Subscription{ + &subscription.Subscription{ Channel: channels[x], - Pair: newPair, + Pair: currency.Pairs{newPair}, Asset: assets[a], }) } @@ -766,9 +766,7 @@ func (f *FTX) GenerateDefaultSubscriptions() ([]subscription.Subscription, error if f.IsWebsocketAuthenticationSupported() { var authchan = []string{wsOrders, wsFills} for x := range authchan { - subscriptions = append(subscriptions, subscription.Subscription{ - Channel: authchan[x], - }) + subscriptions = append(subscriptions, &subscription.Subscription{Channel: authchan[x]}) } } return subscriptions, nil @@ -809,7 +807,7 @@ type WsSub struct { ```go // Subscribe sends a websocket message to receive data from the channel -func (f *FTX) Subscribe(channelsToSubscribe []subscription.Subscription) error { +func (f *FTX) Subscribe(channelsToSubscribe subscription.List) error { // For subscriptions we try to batch as much as possible to limit the amount // of connection usage but sometimes this is not supported on the exchange // API. @@ -825,13 +823,8 @@ channels: case wsFills, wsOrders, wsMarkets: // Authenticated wsFills && wsOrders or wsMarkets which is a channel subscription for the full set of tradable markets do not need a currency pair association. default: - a, err := f.GetPairAssetType(channelsToSubscribe[i].Pair) - if err != nil { - errs = append(errs, err) - continue channels - } // Ensures our outbound currency pair is formatted correctly, sometimes our configuration format is different from what our request format needs to be. - formattedPair, err := f.FormatExchangeCurrency(channelsToSubscribe[i].Pair, a) + formattedPair, err := f.FormatExchangeCurrency(channelsToSubscribe[i].Pair, channelsToSubscribe[i].Asset) if err != nil { errs = append(errs, err) continue channels @@ -846,10 +839,7 @@ channels: // When we have a successful subscription, we can alert our internal management system of the success. f.Websocket.AddSuccessfulSubscriptions(channelsToSubscribe[i]) } - if errs != nil { - return errs - } - return nil + return errs } ``` @@ -1063,7 +1053,7 @@ func (f *FTX) WsAuth(ctx context.Context) error { ```go // Unsubscribe sends a websocket message to stop receiving data from the channel -func (f *FTX) Unsubscribe(channelsToUnsubscribe []subscription.Subscription) error { +func (f *FTX) Unsubscribe(channelsToUnsubscribe subscription.List) error { // As with subscribing we want to batch as much as possible, but sometimes this cannot be achieved due to API shortfalls. var errs common.Errors channels: @@ -1074,13 +1064,7 @@ channels: switch channelsToUnsubscribe[i].Channel { case wsFills, wsOrders, wsMarkets: default: - a, err := f.GetPairAssetType(channelsToUnsubscribe[i].Pair) - if err != nil { - errs = append(errs, err) - continue channels - } - - formattedPair, err := f.FormatExchangeCurrency(channelsToUnsubscribe[i].Pair, a) + formattedPair, err := f.FormatExchangeCurrency(channelsToUnsubscribe[i].Pair, channelsToUnsubscribe[i].Asset) if err != nil { errs = append(errs, err) continue channels @@ -1135,8 +1119,8 @@ func (f *FTX) Setup(exch *config.Exchange) error { Subscriber: f.Subscribe, // Unsubscriber function outlined above. UnSubscriber: f.Unsubscribe, - // GenerateDefaultSubscriptions function outlined above. - GenerateSubscriptions: f.GenerateDefaultSubscriptions, + // GenerateSubscriptions function outlined above. + GenerateSubscriptions: f.generateSubscriptions, // Defines the capabilities of the websocket outlined in supported // features struct. This allows the websocket connection to be flushed // appropriately if we have a pair/asset enable/disable change. This is diff --git a/engine/rpcserver.go b/engine/rpcserver.go index b645b43004a..56d70dc1a1d 100644 --- a/engine/rpcserver.go +++ b/engine/rpcserver.go @@ -3121,7 +3121,7 @@ func (s *RPCServer) WebsocketGetSubscriptions(_ context.Context, r *gctrpc.Webso payload.Subscriptions = append(payload.Subscriptions, &gctrpc.WebsocketSubscription{ Channel: subs[i].Channel, - Pair: subs[i].Pair.String(), + Pairs: subs[i].Pairs.Join(), Asset: subs[i].Asset.String(), Params: string(params), }) diff --git a/exchanges/asset/asset.go b/exchanges/asset/asset.go index f5174fa0869..a90bd3f3c83 100644 --- a/exchanges/asset/asset.go +++ b/exchanges/asset/asset.go @@ -43,9 +43,8 @@ const ( Options OptionCombo FutureCombo - - // Added to represent a USDT and USDC based linear derivatives(futures/perpetual) assets in Bybit V5. - LinearContract + LinearContract // Added to represent a USDT and USDC based linear derivatives(futures/perpetual) assets in Bybit V5 + All optionsFlag = OptionCombo | Options futuresFlag = PerpetualContract | PerpetualSwap | Futures | DeliveryFutures | UpsideProfitContract | DownsideProfitContract | CoinMarginedFutures | USDTMarginedFutures | USDCMarginedFutures | LinearContract | FutureCombo @@ -70,6 +69,7 @@ const ( options = "options" optionCombo = "option_combo" futureCombo = "future_combo" + all = "all" ) var ( @@ -120,6 +120,8 @@ func (a Item) String() string { return optionCombo case FutureCombo: return futureCombo + case All: + return all default: return "" } @@ -225,11 +227,10 @@ func New(input string) (Item, error) { return OptionCombo, nil case futureCombo: return FutureCombo, nil + case all: + return All, nil default: - return 0, fmt.Errorf("%w '%v', only supports %s", - ErrNotSupported, - input, - supportedList) + return 0, fmt.Errorf("%w '%v', only supports %s", ErrNotSupported, input, supportedList) } } diff --git a/exchanges/binance/binance_test.go b/exchanges/binance/binance_test.go index 5e9eda7a2fd..f589a41723b 100644 --- a/exchanges/binance/binance_test.go +++ b/exchanges/binance/binance_test.go @@ -1462,7 +1462,7 @@ func TestGetHistoricTrades(t *testing.T) { if mockTests { expected = 1002 } - assert.Equal(t, expected, len(result), "GetHistoricTrades should return correct number of entries") //nolint:testifylint // assert.Len doesn't produce clear messages on result + assert.Equal(t, expected, len(result), "GetHistoricTrades should return correct number of entries") for _, r := range result { if !assert.WithinRange(t, r.Timestamp, start, end, "All trades should be within time range") { break @@ -1982,7 +1982,7 @@ func BenchmarkWsHandleData(bb *testing.B) { func TestSubscribe(t *testing.T) { t.Parallel() b := b - channels := []subscription.Subscription{ + channels := subscription.List{ {Channel: "btcusdt@ticker"}, {Channel: "btcusdt@trade"}, } @@ -2008,7 +2008,7 @@ func TestSubscribe(t *testing.T) { func TestSubscribeBadResp(t *testing.T) { t.Parallel() - channels := []subscription.Subscription{ + channels := subscription.List{ {Channel: "moons@ticker"}, } mock := func(msg []byte, w *websocket.Conn) error { @@ -2434,19 +2434,19 @@ func TestSeedLocalCache(t *testing.T) { func TestGenerateSubscriptions(t *testing.T) { t.Parallel() - expected := []subscription.Subscription{} + expected := subscription.List{} pairs, err := b.GetEnabledPairs(asset.Spot) assert.NoError(t, err, "GetEnabledPairs should not error") for _, p := range pairs { for _, c := range []string{"kline_1m", "depth@100ms", "ticker", "trade"} { - expected = append(expected, subscription.Subscription{ + expected = append(expected, &subscription.Subscription{ Channel: p.Format(currency.PairFormat{Delimiter: "", Uppercase: false}).String() + "@" + c, - Pair: p, + Pairs: currency.Pairs{p}, Asset: asset.Spot, }) } } - subs, err := b.GenerateSubscriptions() + subs, err := b.generateSubscriptions() assert.NoError(t, err, "GenerateSubscriptions should not error") if assert.Len(t, subs, len(expected), "Should have the correct number of subs") { assert.ElementsMatch(t, subs, expected, "Should get the correct subscriptions") diff --git a/exchanges/binance/binance_websocket.go b/exchanges/binance/binance_websocket.go index 6392f0da966..908a387927e 100644 --- a/exchanges/binance/binance_websocket.go +++ b/exchanges/binance/binance_websocket.go @@ -12,6 +12,7 @@ import ( "github.com/buger/jsonparser" "github.com/gorilla/websocket" + "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/order" @@ -502,8 +503,8 @@ func (b *Binance) UpdateLocalBuffer(wsdp *WebsocketDepthStream) (bool, error) { return false, err } -// GenerateSubscriptions generates the default subscription set -func (b *Binance) GenerateSubscriptions() ([]subscription.Subscription, error) { +// generateSubscriptions generates the default subscription set +func (b *Binance) generateSubscriptions() (subscription.List, error) { var channels = make([]string, 0, len(b.Features.Subscriptions)) for i := range b.Features.Subscriptions { name, err := channelName(b.Features.Subscriptions[i]) @@ -512,7 +513,7 @@ func (b *Binance) GenerateSubscriptions() ([]subscription.Subscription, error) { } channels = append(channels, name) } - var subscriptions []subscription.Subscription + var subscriptions subscription.List pairs, err := b.GetEnabledPairs(asset.Spot) if err != nil { return nil, err @@ -521,9 +522,9 @@ func (b *Binance) GenerateSubscriptions() ([]subscription.Subscription, error) { for z := range channels { lp := pairs[y].Lower() lp.Delimiter = "" - subscriptions = append(subscriptions, subscription.Subscription{ + subscriptions = append(subscriptions, &subscription.Subscription{ Channel: lp.String() + "@" + channels[z], - Pair: pairs[y], + Pairs: currency.Pairs{pairs[y]}, Asset: asset.Spot, }) } @@ -555,22 +556,21 @@ func channelName(s *subscription.Subscription) (string, error) { } // Subscribe subscribes to a set of channels -func (b *Binance) Subscribe(channels []subscription.Subscription) error { +func (b *Binance) Subscribe(channels subscription.List) error { return b.ParallelChanOp(channels, b.subscribeToChan, 50) } // subscribeToChan handles a single subscription and parses the result // on success it adds the subscription to the websocket -func (b *Binance) subscribeToChan(chans []subscription.Subscription) error { +func (b *Binance) subscribeToChan(chans subscription.List) error { id := b.Websocket.Conn.GenerateMessageID(false) cNames := make([]string, len(chans)) for i := range chans { c := chans[i] cNames[i] = c.Channel - c.State = subscription.SubscribingState - if err := b.Websocket.AddSubscription(&c); err != nil { - return fmt.Errorf("%w Channel: %s Pair: %s Error: %w", stream.ErrSubscriptionFailure, c.Channel, c.Pair, err) + if err := b.Websocket.AddSubscriptions(c); err != nil { + return fmt.Errorf("%w Channel: %s Pair: %s Error: %w", stream.ErrSubscriptionFailure, c.Channel, c.Pairs, err) } } @@ -590,23 +590,29 @@ func (b *Binance) subscribeToChan(chans []subscription.Subscription) error { } if err != nil { - b.Websocket.RemoveSubscriptions(chans...) + if err2 := b.Websocket.RemoveSubscriptions(chans...); err2 != nil { + err = common.AppendError(err, err2) + } err = fmt.Errorf("%w: %w; Channels: %s", stream.ErrSubscriptionFailure, err, strings.Join(cNames, ", ")) b.Websocket.DataHandler <- err } else { - b.Websocket.AddSuccessfulSubscriptions(chans...) + for _, s := range chans { + if sErr := s.SetState(subscription.SubscribedState); sErr != nil { + err = common.AppendError(err, sErr) + } + } } return err } // Unsubscribe unsubscribes from a set of channels -func (b *Binance) Unsubscribe(channels []subscription.Subscription) error { +func (b *Binance) Unsubscribe(channels subscription.List) error { return b.ParallelChanOp(channels, b.unsubscribeFromChan, 50) } // unsubscribeFromChan sends a websocket message to stop receiving data from a channel -func (b *Binance) unsubscribeFromChan(chans []subscription.Subscription) error { +func (b *Binance) unsubscribeFromChan(chans subscription.List) error { id := b.Websocket.Conn.GenerateMessageID(false) cNames := make([]string, len(chans)) @@ -633,10 +639,10 @@ func (b *Binance) unsubscribeFromChan(chans []subscription.Subscription) error { err = fmt.Errorf("%w: %w; Channels: %s", stream.ErrUnsubscribeFailure, err, strings.Join(cNames, ", ")) b.Websocket.DataHandler <- err } else { - b.Websocket.RemoveSubscriptions(chans...) + err = b.Websocket.RemoveSubscriptions(chans...) } - return nil + return err } // ProcessUpdate processes the websocket orderbook update diff --git a/exchanges/binance/binance_wrapper.go b/exchanges/binance/binance_wrapper.go index 4c15f27ed00..9ea79f2bd9e 100644 --- a/exchanges/binance/binance_wrapper.go +++ b/exchanges/binance/binance_wrapper.go @@ -188,7 +188,7 @@ func (b *Binance) SetDefaults() { GlobalResultLimit: 1000, }, }, - Subscriptions: []*subscription.Subscription{ + Subscriptions: subscription.List{ {Enabled: true, Channel: subscription.TickerChannel}, {Enabled: true, Channel: subscription.AllTradesChannel}, {Enabled: true, Channel: subscription.CandlesChannel, Interval: kline.OneMin}, @@ -245,7 +245,7 @@ func (b *Binance) Setup(exch *config.Exchange) error { Connector: b.WsConnect, Subscriber: b.Subscribe, Unsubscriber: b.Unsubscribe, - GenerateSubscriptions: b.GenerateSubscriptions, + GenerateSubscriptions: b.generateSubscriptions, Features: &b.Features.Supports.WebsocketCapabilities, OrderbookBufferConfig: buffer.Config{ SortBuffer: true, diff --git a/exchanges/binanceus/binanceus_websocket.go b/exchanges/binanceus/binanceus_websocket.go index b6375885b50..47e48f167d4 100644 --- a/exchanges/binanceus/binanceus_websocket.go +++ b/exchanges/binanceus/binanceus_websocket.go @@ -540,9 +540,9 @@ func (bi *Binanceus) UpdateLocalBuffer(wsdp *WebsocketDepthStream) (bool, error) } // GenerateSubscriptions generates the default subscription set -func (bi *Binanceus) GenerateSubscriptions() ([]subscription.Subscription, error) { +func (bi *Binanceus) GenerateSubscriptions() (subscription.List, error) { var channels = []string{"@ticker", "@trade", "@kline_1m", "@depth@100ms"} - var subscriptions []subscription.Subscription + var subscriptions subscription.List pairs, err := bi.GetEnabledPairs(asset.Spot) if err != nil { @@ -558,9 +558,9 @@ subs: log.Warnf(log.WebsocketMgr, "BinanceUS has 1024 subscription limit, only subscribing within limit. Requested %v", len(pairs)*len(channels)) break subs } - subscriptions = append(subscriptions, subscription.Subscription{ + subscriptions = append(subscriptions, &subscription.Subscription{ Channel: lp.String() + channels[z], - Pair: pairs[y], + Pairs: currency.Pairs{pairs[y]}, Asset: asset.Spot, }) } @@ -570,7 +570,7 @@ subs: } // Subscribe subscribes to a set of channels -func (bi *Binanceus) Subscribe(channelsToSubscribe []subscription.Subscription) error { +func (bi *Binanceus) Subscribe(channelsToSubscribe subscription.List) error { payload := WebsocketPayload{ Method: "SUBSCRIBE", } @@ -590,12 +590,11 @@ func (bi *Binanceus) Subscribe(channelsToSubscribe []subscription.Subscription) return err } } - bi.Websocket.AddSuccessfulSubscriptions(channelsToSubscribe...) - return nil + return bi.Websocket.AddSuccessfulSubscriptions(channelsToSubscribe...) } // Unsubscribe unsubscribes from a set of channels -func (bi *Binanceus) Unsubscribe(channelsToUnsubscribe []subscription.Subscription) error { +func (bi *Binanceus) Unsubscribe(channelsToUnsubscribe subscription.List) error { payload := WebsocketPayload{ Method: "UNSUBSCRIBE", } @@ -615,8 +614,7 @@ func (bi *Binanceus) Unsubscribe(channelsToUnsubscribe []subscription.Subscripti return err } } - bi.Websocket.RemoveSubscriptions(channelsToUnsubscribe...) - return nil + return bi.Websocket.RemoveSubscriptions(channelsToUnsubscribe...) } func (bi *Binanceus) setupOrderbookManager() { diff --git a/exchanges/bitfinex/bitfinex_test.go b/exchanges/bitfinex/bitfinex_test.go index d6767a49cf4..30a2c85b35f 100644 --- a/exchanges/bitfinex/bitfinex_test.go +++ b/exchanges/bitfinex/bitfinex_test.go @@ -14,7 +14,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/thrasher-corp/gocryptotrader/common" - "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/core" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" @@ -37,29 +36,21 @@ const ( canManipulateRealOrders = false ) -var b = &Bitfinex{} +var b *Bitfinex var wsConnected bool var btcusdPair = currency.NewPair(currency.BTC, currency.USD) func TestMain(m *testing.M) { - b.SetDefaults() - cfg := config.GetConfig() - err := cfg.LoadConfig("../../testdata/configtest.json", true) - if err != nil { - log.Fatal("Bitfinex load config error", err) - } - bfxConfig, err := cfg.GetExchangeConfig("Bitfinex") - if err != nil { - log.Fatal("Bitfinex Setup() init error") - } - b.Websocket = sharedtestvalues.NewTestWebsocket() - err = b.Setup(bfxConfig) - if err != nil { - log.Fatal("Bitfinex setup error", err) + b = new(Bitfinex) + if err := testexch.Setup(b); err != nil { + log.Fatal(err) } + if apiKey != "" { + b.Websocket.SetCanUseAuthenticatedEndpoints(true) b.SetCredentials(apiKey, apiSecret, "", "", "", "") } + if !b.Enabled || len(b.BaseCurrencies) < 1 { log.Fatal("Bitfinex Setup values not set correctly") } @@ -1126,13 +1117,13 @@ func TestWsAuth(t *testing.T) { if !b.API.AuthenticatedWebsocketSupport { t.Skip("Authentecated API support not enabled") } - setupWs(t) - assert.True(t, b.Websocket.CanUseAuthenticatedEndpoints(), "CanUseAuthenticatedEndpoints should be turned on") + testexch.SetupWs(t, b) + require.True(t, b.Websocket.CanUseAuthenticatedEndpoints(), "CanUseAuthenticatedEndpoints should be turned on") var resp map[string]interface{} catcher := func() (ok bool) { select { - case v := <-b.Websocket.DataHandler: + case v := <-b.Websocket.ToRoutine: resp, ok = v.(map[string]interface{}) default: } @@ -1149,24 +1140,26 @@ func TestWsAuth(t *testing.T) { // TestWsSubscribe tests Subscribe and Unsubscribe functionality // See also TestSubscribeReq which covers key and symbol conversion func TestWsSubscribe(t *testing.T) { - setupWs(t) - err := b.Subscribe([]subscription.Subscription{{Channel: wsTicker, Pair: currency.NewPair(currency.BTC, currency.USD), Asset: asset.Spot}}) - assert.NoError(t, err, "Subrcribe should not error") + b := new(Bitfinex) //nolint:govet // Intentional shadow of b to avoid future copy/paste mistakes + require.NoError(t, testexch.Setup(b), "TestInstance must not error") + testexch.SetupWs(t, b) + err := b.Subscribe(subscription.List{{Channel: wsTicker, Pairs: currency.Pairs{currency.NewPair(currency.BTC, currency.USD)}, Asset: asset.Spot}}) + require.NoError(t, err, "Subrcribe should not error") catcher := func() (ok bool) { - i := <-b.Websocket.DataHandler + i := <-b.Websocket.ToRoutine _, ok = i.(*ticker.Price) return } assert.Eventually(t, catcher, sharedtestvalues.WebsocketResponseDefaultTimeout, time.Millisecond*10, "Ticker response should arrive") subs, err := b.GetSubscriptions() - assert.NoError(t, err, "GetSubscriptions should not error") - assert.Len(t, subs, 1, "We should only have 1 subscription; subID subscription should have been Removed by subscribeToChan") + require.NoError(t, err, "GetSubscriptions should not error") + require.Len(t, subs, 1, "We should only have 1 subscription; subID subscription should have been Removed by subscribeToChan") - err = b.Subscribe([]subscription.Subscription{{Channel: wsTicker, Pair: currency.NewPair(currency.BTC, currency.USD), Asset: asset.Spot}}) - assert.ErrorIs(t, err, stream.ErrSubscriptionFailure, "Duplicate subscription should error correctly") + err = b.Subscribe(subscription.List{{Channel: wsTicker, Pairs: currency.Pairs{currency.NewPair(currency.BTC, currency.USD)}, Asset: asset.Spot}}) + require.ErrorIs(t, err, stream.ErrSubscriptionFailure, "Duplicate subscription should error correctly") catcher = func() bool { - i := <-b.Websocket.DataHandler + i := <-b.Websocket.ToRoutine if e, ok := i.(error); ok { if assert.ErrorIs(t, e, stream.ErrSubscriptionFailure, "Error should go to DataHandler") { assert.ErrorContains(t, e, "subscribe: dup (code: 10301)", "Error should contain message and code") @@ -1178,8 +1171,8 @@ func TestWsSubscribe(t *testing.T) { assert.Eventually(t, catcher, sharedtestvalues.WebsocketResponseDefaultTimeout, time.Millisecond*10, "error response should arrive") subs, err = b.GetSubscriptions() - assert.NoError(t, err, "GetSubscriptions should not error") - assert.Len(t, subs, 1, "We should only have one subscription after an error attempt") + require.NoError(t, err, "GetSubscriptions should not error") + require.Len(t, subs, 1, "We should only have one subscription after an error attempt") err = b.Unsubscribe(subs) assert.NoError(t, err, "Unsubscribing should not error") @@ -1192,9 +1185,9 @@ func TestWsSubscribe(t *testing.T) { assert.ErrorContains(t, err, strconv.Itoa(chanID), "Unsubscribe should contain correct chanId") assert.ErrorContains(t, err, "unsubscribe: invalid (code: 10400)", "Unsubscribe should contain correct upstream error") - err = b.Subscribe([]subscription.Subscription{{ + err = b.Subscribe(subscription.List{{ Channel: wsTicker, - Pair: currency.NewPair(currency.BTC, currency.USD), + Pairs: currency.Pairs{currency.NewPair(currency.BTC, currency.USD)}, Asset: asset.Spot, Params: map[string]interface{}{"key": "tBTCUSD"}, }}) @@ -1206,7 +1199,7 @@ func TestWsSubscribe(t *testing.T) { func TestSubscribeReq(t *testing.T) { c := &subscription.Subscription{ Channel: wsCandles, - Pair: currency.NewPair(currency.BTC, currency.USD), + Pairs: currency.Pairs{currency.NewPair(currency.BTC, currency.USD)}, Asset: asset.MarginFunding, Params: map[string]interface{}{ CandlesPeriodKey: "30", @@ -1225,14 +1218,14 @@ func TestSubscribeReq(t *testing.T) { c = &subscription.Subscription{ Channel: wsBook, - Pair: currency.NewPair(currency.BTC, currency.DOGE), + Pairs: currency.Pairs{currency.NewPair(currency.BTC, currency.DOGE)}, Asset: asset.Spot, } r, err = subscribeReq(c) assert.NoError(t, err, "subscribeReq should not error") assert.Equal(t, "tBTC:DOGE", r["symbol"], "symbol should use colon delimiter if a currency is > 3 chars") - c.Pair = currency.NewPair(currency.BTC, currency.LTC) + c.Pairs = currency.Pairs{currency.NewPair(currency.BTC, currency.LTC)} r, err = subscribeReq(c) assert.NoError(t, err, "subscribeReq should not error") assert.Equal(t, "tBTCLTC", r["symbol"], "symbol should not use colon delimiter if both currencies < 3 chars") @@ -1241,7 +1234,7 @@ func TestSubscribeReq(t *testing.T) { // TestWsPlaceOrder dials websocket, sends order request. func TestWsPlaceOrder(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, b, canManipulateRealOrders) - setupWs(t) + testexch.SetupWs(t, b) _, err := b.WsNewOrder(&WsNewOrderRequest{ GroupID: 1, @@ -1258,7 +1251,7 @@ func TestWsPlaceOrder(t *testing.T) { // TestWsCancelOrder dials websocket, sends cancel request. func TestWsCancelOrder(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, b, canManipulateRealOrders) - setupWs(t) + testexch.SetupWs(t, b) if err := b.WsCancelOrder(1234); err != nil { t.Error(err) } @@ -1267,7 +1260,7 @@ func TestWsCancelOrder(t *testing.T) { // TestWsCancelOrder dials websocket, sends modify request. func TestWsUpdateOrder(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, b, canManipulateRealOrders) - setupWs(t) + testexch.SetupWs(t, b) err := b.WsModifyOrder(&WsUpdateOrderRequest{ OrderID: 1234, Price: -111, @@ -1281,7 +1274,7 @@ func TestWsUpdateOrder(t *testing.T) { // TestWsCancelAllOrders dials websocket, sends cancel all request. func TestWsCancelAllOrders(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, b, canManipulateRealOrders) - setupWs(t) + testexch.SetupWs(t, b) if err := b.WsCancelAllOrders(); err != nil { t.Error(err) } @@ -1290,7 +1283,7 @@ func TestWsCancelAllOrders(t *testing.T) { // TestWsCancelAllOrders dials websocket, sends cancel all request. func TestWsCancelMultiOrders(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, b, canManipulateRealOrders) - setupWs(t) + testexch.SetupWs(t, b) err := b.WsCancelMultiOrders([]int64{1, 2, 3, 4}) if err != nil { t.Error(err) @@ -1300,7 +1293,7 @@ func TestWsCancelMultiOrders(t *testing.T) { // TestWsNewOffer dials websocket, sends new offer request. func TestWsNewOffer(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, b, canManipulateRealOrders) - setupWs(t) + testexch.SetupWs(t, b) err := b.WsNewOffer(&WsNewOfferRequest{ Type: order.Limit.String(), Symbol: "fBTC", @@ -1316,7 +1309,7 @@ func TestWsNewOffer(t *testing.T) { // TestWsCancelOffer dials websocket, sends cancel offer request. func TestWsCancelOffer(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, b, canManipulateRealOrders) - setupWs(t) + testexch.SetupWs(t, b) if err := b.WsCancelOffer(1234); err != nil { t.Error(err) } @@ -1332,7 +1325,8 @@ func TestWsSubscribedResponse(t *testing.T) { assert.ErrorContains(t, err, "waiter1", "Should error containing subID if") } - b.Websocket.AddSuccessfulSubscriptions(subscription.Subscription{Key: "waiter1"}) + err = b.Websocket.AddSubscriptions(&subscription.Subscription{Key: "waiter1"}) + require.NoError(t, err, "AddSubscriptions must not error") err = b.wsHandleData([]byte(`{"event":"subscribed","channel":"ticker","chanId":224555,"subId":"waiter1","symbol":"tBTCUSD","pair":"BTCUSD"}`)) assert.NoError(t, err, "wsHandleData should not error") if assert.NotEmpty(t, m.C, "Matcher should have received a sub notification") { @@ -1345,9 +1339,10 @@ func TestWsSubscribedResponse(t *testing.T) { } func TestWsOrderBook(t *testing.T) { - b.Websocket.AddSuccessfulSubscriptions(subscription.Subscription{Key: 23405, Asset: asset.Spot, Pair: btcusdPair, Channel: wsBook}) + err := b.Websocket.AddSubscriptions(&subscription.Subscription{Key: 23405, Asset: asset.Spot, Pairs: currency.Pairs{btcusdPair}, Channel: wsBook}) + require.NoError(t, err, "AddSubscriptions must not error") pressXToJSON := `[23405,[[38334303613,9348.8,0.53],[38334308111,9348.8,5.98979404],[38331335157,9344.1,1.28965787],[38334302803,9343.8,0.08230094],[38334279092,9343,0.8],[38334307036,9342.938663676,0.8],[38332749107,9342.9,0.2],[38332277330,9342.8,0.85],[38329406786,9342,0.1432012],[38332841570,9341.947288638,0.3],[38332163238,9341.7,0.3],[38334303384,9341.6,0.324],[38332464840,9341.4,0.5],[38331935870,9341.2,0.5],[38334312082,9340.9,0.02126899],[38334261292,9340.8,0.26763],[38334138680,9340.625455254,0.12],[38333896802,9339.8,0.85],[38331627527,9338.9,1.57863959],[38334186713,9338.9,0.26769],[38334305819,9338.8,2.999],[38334211180,9338.75285796,3.999],[38334310699,9337.8,0.10679883],[38334307414,9337.5,1],[38334179822,9337.1,0.26773],[38334306600,9336.659955102,1.79],[38334299667,9336.6,1.1],[38334306452,9336.6,0.13979771],[38325672859,9336.3,1.25],[38334311646,9336.2,1],[38334258509,9336.1,0.37],[38334310592,9336,1.79],[38334310378,9335.6,1.43],[38334132444,9335.2,0.26777],[38331367325,9335,0.07],[38334310703,9335,0.10680562],[38334298209,9334.7,0.08757301],[38334304857,9334.456899462,0.291],[38334309940,9334.088390727,0.0725],[38334310377,9333.7,1.2868],[38334297615,9333.607784,0.1108],[38334095188,9333.3,0.26785],[38334228913,9332.7,0.40861186],[38334300526,9332.363996604,0.3884],[38334310701,9332.2,0.10680562],[38334303548,9332.005382871,0.07],[38334311798,9331.8,0.41285228],[38334301012,9331.7,1.7952],[38334089877,9331.4,0.2679],[38321942150,9331.2,0.2],[38334310670,9330,1.069],[38334063096,9329.6,0.26796],[38334310700,9329.4,0.10680562],[38334310404,9329.3,1],[38334281630,9329.1,6.57150597],[38334036864,9327.7,0.26801],[38334310702,9326.6,0.10680562],[38334311799,9326.1,0.50220625],[38334164163,9326,0.219638],[38334309722,9326,1.5],[38333051682,9325.8,0.26807],[38334302027,9325.7,0.75],[38334203435,9325.366592,0.32397696],[38321967613,9325,0.05],[38334298787,9324.9,0.3],[38334301719,9324.8,3.6227592],[38331316716,9324.763454646,0.71442],[38334310698,9323.8,0.10680562],[38334035499,9323.7,0.23431017],[38334223472,9322.670551788,0.42150603],[38334163459,9322.560399006,0.143967],[38321825171,9320.8,2],[38334075805,9320.467496148,0.30772633],[38334075800,9319.916732238,0.61457592],[38333682302,9319.7,0.0011],[38331323088,9319.116771762,0.12913],[38333677480,9319,0.0199],[38334277797,9318.6,0.89],[38325235155,9318.041088,1.20249],[38334310910,9317.82382938,1.79],[38334311811,9317.2,0.61079138],[38334311812,9317.2,0.71937652],[38333298214,9317.1,50],[38334306359,9317,1.79],[38325531545,9316.382823951,0.21263],[38333727253,9316.3,0.02316372],[38333298213,9316.1,45],[38333836479,9316,2.135],[38324520465,9315.9,2.7681],[38334307411,9315.5,1],[38330313617,9315.3,0.84455],[38334077770,9315.294024,0.01248397],[38334286663,9315.294024,1],[38325533762,9315.290315394,2.40498],[38334310018,9315.2,3],[38333682617,9314.6,0.0011],[38334304794,9314.6,0.76364676],[38334304798,9314.3,0.69242113],[38332915733,9313.8,0.0199],[38334084411,9312.8,1],[38334311893,9350.1,-1.015],[38334302734,9350.3,-0.26737],[38334300732,9350.8,-5.2],[38333957619,9351,-0.90677089],[38334300521,9351,-1.6457],[38334301600,9351.012829557,-0.0523],[38334308878,9351.7,-2.5],[38334299570,9351.921544,-0.1015],[38334279367,9352.1,-0.26732],[38334299569,9352.411802928,-0.4036],[38334202773,9353.4,-0.02139404],[38333918472,9353.7,-1.96412776],[38334278782,9354,-0.26731],[38334278606,9355,-1.2785],[38334302105,9355.439221251,-0.79191542],[38313897370,9355.569409242,-0.43363],[38334292995,9355.584296,-0.0979],[38334216989,9355.8,-0.03686414],[38333894025,9355.9,-0.26721],[38334293798,9355.936691952,-0.4311],[38331159479,9356,-0.4204022],[38333918888,9356.1,-1.10885563],[38334298205,9356.4,-0.20124428],[38328427481,9356.5,-0.1],[38333343289,9356.6,-0.41034213],[38334297205,9356.6,-0.08835018],[38334277927,9356.741101161,-0.0737],[38334311645,9356.8,-0.5],[38334309002,9356.9,-5],[38334309736,9357,-0.10680107],[38334306448,9357.4,-0.18645275],[38333693302,9357.7,-0.2672],[38332815159,9357.8,-0.0011],[38331239824,9358.2,-0.02],[38334271608,9358.3,-2.999],[38334311971,9358.4,-0.55],[38333919260,9358.5,-1.9972841],[38334265365,9358.5,-1.7841],[38334277960,9359,-3],[38334274601,9359.020969848,-3],[38326848839,9359.1,-0.84],[38334291080,9359.247048,-0.16199869],[38326848844,9359.4,-1.84],[38333680200,9359.6,-0.26713],[38331326606,9359.8,-0.84454],[38334309738,9359.8,-0.10680107],[38331314707,9359.9,-0.2],[38333919803,9360.9,-1.41177599],[38323651149,9361.33417827,-0.71442],[38333656906,9361.5,-0.26705],[38334035500,9361.5,-0.40861586],[38334091886,9362.4,-6.85940815],[38334269617,9362.5,-4],[38323629409,9362.545858872,-2.40497],[38334309737,9362.7,-0.10680107],[38334312380,9362.7,-3],[38325280830,9362.8,-1.75123],[38326622800,9362.8,-1.05145],[38333175230,9363,-0.0011],[38326848745,9363.2,-0.79],[38334308960,9363.206775564,-0.12],[38333920234,9363.3,-1.25318113],[38326848843,9363.4,-1.29],[38331239823,9363.4,-0.02],[38333209613,9363.4,-0.26719],[38334299964,9364,-0.05583123],[38323470224,9364.161816648,-0.12912],[38334284711,9365,-0.21346019],[38334299594,9365,-2.6757062],[38323211816,9365.073132585,-0.21262],[38334312456,9365.1,-0.11167861],[38333209612,9365.2,-0.26719],[38327770474,9365.3,-0.0073],[38334298788,9365.3,-0.3],[38334075803,9365.409831204,-0.30772637],[38334309740,9365.5,-0.10680107],[38326608767,9365.7,-2.76809],[38333920657,9365.7,-1.25848083],[38329594226,9366.6,-0.02587],[38334311813,9366.7,-4.72290945],[38316386301,9367.39258128,-2.37581],[38334302026,9367.4,-4.5],[38334228915,9367.9,-0.81725458],[38333921381,9368.1,-1.72213641],[38333175678,9368.2,-0.0011],[38334301150,9368.2,-2.654604],[38334297208,9368.3,-0.78036466],[38334309739,9368.3,-0.10680107],[38331227515,9368.7,-0.02],[38331184470,9369,-0.003975],[38334203436,9369.319616,-0.32397695],[38334269964,9369.7,-0.5],[38328386732,9370,-4.11759935],[38332719555,9370,-0.025],[38333921935,9370.5,-1.2224398],[38334258511,9370.5,-0.35],[38326848842,9370.8,-0.34],[38333985038,9370.9,-0.8551502],[38334283018,9370.9,-1],[38326848744,9371,-1.34]],5]` - err := b.wsHandleData([]byte(pressXToJSON)) + err = b.wsHandleData([]byte(pressXToJSON)) if err != nil { t.Error(err) } @@ -1362,18 +1357,20 @@ func TestWsOrderBook(t *testing.T) { } func TestWsTradeResponse(t *testing.T) { - b.Websocket.AddSuccessfulSubscriptions(subscription.Subscription{Asset: asset.Spot, Pair: btcusdPair, Channel: wsTrades, Key: 18788}) + err := b.Websocket.AddSubscriptions(&subscription.Subscription{Asset: asset.Spot, Pairs: currency.Pairs{btcusdPair}, Channel: wsTrades, Key: 18788}) + require.NoError(t, err, "AddSubscriptions must not error") pressXToJSON := `[18788,[[412685577,1580268444802,11.1998,176.3],[412685575,1580268444802,5,176.29952759],[412685574,1580268374717,1.99069999,176.41],[412685573,1580268374717,1.00930001,176.41],[412685572,1580268358760,0.9907,176.47],[412685571,1580268324362,0.5505,176.44],[412685570,1580268297270,-0.39040819,176.39],[412685568,1580268297270,-0.39780162,176.46475676],[412685567,1580268283470,-0.09,176.41],[412685566,1580268256536,-2.31310783,176.48],[412685565,1580268256536,-0.59669217,176.49],[412685564,1580268256536,-0.9902,176.49],[412685562,1580268194474,0.9902,176.55],[412685561,1580268186215,0.1,176.6],[412685560,1580268185964,-2.17096773,176.5],[412685559,1580268185964,-1.82903227,176.51],[412685558,1580268181215,2.098914,176.53],[412685557,1580268169844,16.7302,176.55],[412685556,1580268169844,3.25,176.54],[412685555,1580268155725,0.23576115,176.45],[412685553,1580268155725,3,176.44596249],[412685552,1580268155725,3.25,176.44],[412685551,1580268155725,5,176.44],[412685550,1580268155725,0.65830078,176.41],[412685549,1580268155725,0.45063807,176.41],[412685548,1580268153825,-0.67604704,176.39],[412685547,1580268145713,2.5883,176.41],[412685543,1580268087513,12.92927,176.33],[412685542,1580268087513,0.40083,176.33],[412685533,1580268005756,-0.17096773,176.32]]]` - err := b.wsHandleData([]byte(pressXToJSON)) + err = b.wsHandleData([]byte(pressXToJSON)) if err != nil { t.Error(err) } } func TestWsTickerResponse(t *testing.T) { - b.Websocket.AddSuccessfulSubscriptions(subscription.Subscription{Asset: asset.Spot, Pair: btcusdPair, Channel: wsTicker, Key: 11534}) + err := b.Websocket.AddSubscriptions(&subscription.Subscription{Asset: asset.Spot, Pairs: currency.Pairs{btcusdPair}, Channel: wsTicker, Key: 11534}) + require.NoError(t, err, "AddSubscriptions must not error") pressXToJSON := `[11534,[61.304,2228.36155358,61.305,1323.2442970500003,0.395,0.0065,61.371,50973.3020771,62.5,57.421]]` - err := b.wsHandleData([]byte(pressXToJSON)) + err = b.wsHandleData([]byte(pressXToJSON)) if err != nil { t.Error(err) } @@ -1381,7 +1378,8 @@ func TestWsTickerResponse(t *testing.T) { if err != nil { t.Error(err) } - b.Websocket.AddSuccessfulSubscriptions(subscription.Subscription{Asset: asset.Spot, Pair: pair, Channel: wsTicker, Key: 123412}) + err = b.Websocket.AddSubscriptions(&subscription.Subscription{Asset: asset.Spot, Pairs: currency.Pairs{pair}, Channel: wsTicker, Key: 123412}) + require.NoError(t, err, "AddSubscriptions must not error") pressXToJSON = `[123412,[61.304,2228.36155358,61.305,1323.2442970500003,0.395,0.0065,61.371,50973.3020771,62.5,57.421]]` err = b.wsHandleData([]byte(pressXToJSON)) if err != nil { @@ -1391,7 +1389,8 @@ func TestWsTickerResponse(t *testing.T) { if err != nil { t.Error(err) } - b.Websocket.AddSuccessfulSubscriptions(subscription.Subscription{Asset: asset.Spot, Pair: pair, Channel: wsTicker, Key: 123413}) + err = b.Websocket.AddSubscriptions(&subscription.Subscription{Asset: asset.Spot, Pairs: currency.Pairs{pair}, Channel: wsTicker, Key: 123413}) + require.NoError(t, err, "AddSubscriptions must not error") pressXToJSON = `[123413,[61.304,2228.36155358,61.305,1323.2442970500003,0.395,0.0065,61.371,50973.3020771,62.5,57.421]]` err = b.wsHandleData([]byte(pressXToJSON)) if err != nil { @@ -1401,7 +1400,8 @@ func TestWsTickerResponse(t *testing.T) { if err != nil { t.Error(err) } - b.Websocket.AddSuccessfulSubscriptions(subscription.Subscription{Asset: asset.Spot, Pair: pair, Channel: wsTicker, Key: 123414}) + err = b.Websocket.AddSubscriptions(&subscription.Subscription{Asset: asset.Spot, Pairs: currency.Pairs{pair}, Channel: wsTicker, Key: 123414}) + require.NoError(t, err, "AddSubscriptions must not error") pressXToJSON = `[123414,[61.304,2228.36155358,61.305,1323.2442970500003,0.395,0.0065,61.371,50973.3020771,62.5,57.421]]` err = b.wsHandleData([]byte(pressXToJSON)) if err != nil { @@ -1410,9 +1410,10 @@ func TestWsTickerResponse(t *testing.T) { } func TestWsCandleResponse(t *testing.T) { - b.Websocket.AddSuccessfulSubscriptions(subscription.Subscription{Asset: asset.Spot, Pair: btcusdPair, Channel: wsCandles, Key: 343351}) + err := b.Websocket.AddSubscriptions(&subscription.Subscription{Asset: asset.Spot, Pairs: currency.Pairs{btcusdPair}, Channel: wsCandles, Key: 343351}) + require.NoError(t, err, "AddSubscriptions must not error") pressXToJSON := `[343351,[[1574698260000,7379.785503,7383.8,7388.3,7379.785503,1.68829482]]]` - err := b.wsHandleData([]byte(pressXToJSON)) + err = b.wsHandleData([]byte(pressXToJSON)) if err != nil { t.Error(err) } @@ -1989,29 +1990,6 @@ func TestGetErrResp(t *testing.T) { assert.NoError(t, fixture.Close(), "Closing the fixture file should not error") } -// setupWs is a helper function to connect both auth and normal websockets -// It will skip the test if websockets are not enabled -// It's up to the test to skip if it requires creds, though -func setupWs(tb testing.TB) { - tb.Helper() - if !b.Websocket.IsEnabled() { - tb.Skip("Websocket not enabled") - } - if b.Websocket.IsConnected() { - return - } - if wsConnected { - return - } - // We don't use b.websocket.Connect() because it'd subscribe to channels - err := b.WsConnect() - if !assert.NoError(tb, err, "WsConnect should not error") { - tb.FailNow() - } - - wsConnected = true -} - func TestGetCurrencyTradeURL(t *testing.T) { t.Parallel() testexch.UpdatePairsOnce(t, b) diff --git a/exchanges/bitfinex/bitfinex_websocket.go b/exchanges/bitfinex/bitfinex_websocket.go index 0ba632ee10a..ed04da9988f 100644 --- a/exchanges/bitfinex/bitfinex_websocket.go +++ b/exchanges/bitfinex/bitfinex_websocket.go @@ -106,10 +106,7 @@ func (b *Bitfinex) WsDataHandler() { select { case b.Websocket.DataHandler <- err: default: - log.Errorf(log.WebsocketMgr, - "%s websocket handle data error: %v", - b.Name, - err) + log.Errorf(log.WebsocketMgr, "%s websocket handle data error: %v", b.Name, err) } } default: @@ -149,7 +146,7 @@ func (b *Bitfinex) wsHandleData(respRaw []byte) error { return b.handleWSChannelUpdate(c, eventType, d) } if b.Verbose { - log.Warnf(log.ExchangeSys, "%s %s; dropped WS message: %s", b.Name, stream.ErrSubscriptionNotFound, respRaw) + log.Warnf(log.ExchangeSys, "%s %s; dropped WS message: %s", b.Name, subscription.ErrNotFound, respRaw) } // We didn't have a mapping for this chanID; This probably means we have unsubscribed OR // received our first message before processing the sub chanID @@ -501,22 +498,25 @@ func (b *Bitfinex) handleWSSubscribed(respRaw []byte) error { c := b.Websocket.GetSubscription(subID) if c == nil { - return fmt.Errorf("%w: %w subID: %s", stream.ErrSubscriptionFailure, stream.ErrSubscriptionNotFound, subID) + return fmt.Errorf("%w: %w subID: %s", stream.ErrSubscriptionFailure, subscription.ErrNotFound, subID) } chanID, err := jsonparser.GetInt(respRaw, "chanId") if err != nil { - return fmt.Errorf("%w: %w 'chanId': %w; Channel: %s Pair: %s", stream.ErrSubscriptionFailure, errParsingWSField, err, c.Channel, c.Pair) + return fmt.Errorf("%w: %w 'chanId': %w; Channel: %s Pair: %s", stream.ErrSubscriptionFailure, errParsingWSField, err, c.Channel, c.Pairs) } // Note: chanID's int type avoids conflicts with the string type subID key because of the type difference + c = c.Clone() c.Key = int(chanID) // subscribeToChan removes the old subID keyed Subscription - b.Websocket.AddSuccessfulSubscriptions(*c) + if err := b.Websocket.AddSuccessfulSubscriptions(c); err != nil { + return fmt.Errorf("%w: %w subID: %s", stream.ErrSubscriptionFailure, err, subID) + } if b.Verbose { - log.Debugf(log.ExchangeSys, "%s Subscribed to Channel: %s Pair: %s ChannelID: %d\n", b.Name, c.Channel, c.Pair, chanID) + log.Debugf(log.ExchangeSys, "%s Subscribed to Channel: %s Pair: %s ChannelID: %d\n", b.Name, c.Channel, c.Pairs, chanID) } if !b.Websocket.Match.IncomingWithData("subscribe:"+subID, respRaw) { return fmt.Errorf("%v channel subscribe listener not found", subID) @@ -525,6 +525,10 @@ func (b *Bitfinex) handleWSSubscribed(respRaw []byte) error { } func (b *Bitfinex) handleWSChannelUpdate(c *subscription.Subscription, eventType string, d []interface{}) error { + if c == nil { + return fmt.Errorf("%w: Subscription param", common.ErrNilPointer) + } + if eventType == wsChecksum { return b.handleWSChecksum(c, d) } @@ -533,6 +537,10 @@ func (b *Bitfinex) handleWSChannelUpdate(c *subscription.Subscription, eventType return nil } + if len(c.Pairs) != 1 { + return subscription.ErrNotSinglePair + } + switch c.Channel { case wsBook: return b.handleWSBookUpdate(c, d) @@ -548,6 +556,9 @@ func (b *Bitfinex) handleWSChannelUpdate(c *subscription.Subscription, eventType } func (b *Bitfinex) handleWSChecksum(c *subscription.Subscription, d []interface{}) error { + if c == nil { + return fmt.Errorf("%w: Subscription param", common.ErrNilPointer) + } var token int if f, ok := d[2].(float64); !ok { return common.GetTypeAssertError("float64", d[2], "checksum") @@ -579,6 +590,12 @@ func (b *Bitfinex) handleWSChecksum(c *subscription.Subscription, d []interface{ } func (b *Bitfinex) handleWSBookUpdate(c *subscription.Subscription, d []interface{}) error { + if c == nil { + return fmt.Errorf("%w: Subscription param", common.ErrNilPointer) + } + if len(c.Pairs) != 1 { + return subscription.ErrNotSinglePair + } var newOrderbook []WebsocketBook obSnapBundle, ok := d[1].([]interface{}) if !ok { @@ -632,7 +649,7 @@ func (b *Bitfinex) handleWSBookUpdate(c *subscription.Subscription, d []interfac Amount: rateAmount}) } } - if err := b.WsInsertSnapshot(c.Pair, c.Asset, newOrderbook, fundingRate); err != nil { + if err := b.WsInsertSnapshot(c.Pairs[0], c.Asset, newOrderbook, fundingRate); err != nil { return fmt.Errorf("inserting snapshot error: %s", err) } @@ -664,7 +681,7 @@ func (b *Bitfinex) handleWSBookUpdate(c *subscription.Subscription, d []interfac Amount: amountRate}) } - if err := b.WsUpdateOrderbook(c, c.Pair, c.Asset, newOrderbook, int64(sequenceNo), fundingRate); err != nil { + if err := b.WsUpdateOrderbook(c, c.Pairs[0], c.Asset, newOrderbook, int64(sequenceNo), fundingRate); err != nil { return fmt.Errorf("updating orderbook error: %s", err) } @@ -674,6 +691,12 @@ func (b *Bitfinex) handleWSBookUpdate(c *subscription.Subscription, d []interfac } func (b *Bitfinex) handleWSCandleUpdate(c *subscription.Subscription, d []interface{}) error { + if c == nil { + return fmt.Errorf("%w: Subscription param", common.ErrNilPointer) + } + if len(c.Pairs) != 1 { + return subscription.ErrNotSinglePair + } candleBundle, ok := d[1].([]interface{}) if !ok || len(candleBundle) == 0 { return nil @@ -712,7 +735,7 @@ func (b *Bitfinex) handleWSCandleUpdate(c *subscription.Subscription, d []interf } klineData.Exchange = b.Name klineData.AssetType = c.Asset - klineData.Pair = c.Pair + klineData.Pair = c.Pairs[0] b.Websocket.DataHandler <- klineData } case float64: @@ -741,13 +764,19 @@ func (b *Bitfinex) handleWSCandleUpdate(c *subscription.Subscription, d []interf } klineData.Exchange = b.Name klineData.AssetType = c.Asset - klineData.Pair = c.Pair + klineData.Pair = c.Pairs[0] b.Websocket.DataHandler <- klineData } return nil } func (b *Bitfinex) handleWSTickerUpdate(c *subscription.Subscription, d []interface{}) error { + if c == nil { + return fmt.Errorf("%w: Subscription param", common.ErrNilPointer) + } + if len(c.Pairs) != 1 { + return subscription.ErrNotSinglePair + } tickerData, ok := d[1].([]interface{}) if !ok { return errors.New("type assertion for tickerData") @@ -755,7 +784,7 @@ func (b *Bitfinex) handleWSTickerUpdate(c *subscription.Subscription, d []interf t := &ticker.Price{ AssetType: c.Asset, - Pair: c.Pair, + Pair: c.Pairs[0], ExchangeName: b.Name, } @@ -821,6 +850,12 @@ func (b *Bitfinex) handleWSTickerUpdate(c *subscription.Subscription, d []interf } func (b *Bitfinex) handleWSTradesUpdate(c *subscription.Subscription, eventType string, d []interface{}) error { + if c == nil { + return fmt.Errorf("%w: Subscription param", common.ErrNilPointer) + } + if len(c.Pairs) != 1 { + return subscription.ErrNotSinglePair + } if !b.IsSaveTradeDataEnabled() { return nil } @@ -936,7 +971,7 @@ func (b *Bitfinex) handleWSTradesUpdate(c *subscription.Subscription, eventType } trades[i] = trade.Data{ TID: strconv.FormatInt(tradeHolder[i].ID, 10), - CurrencyPair: c.Pair, + CurrencyPair: c.Pairs[0], Timestamp: time.UnixMilli(tradeHolder[i].Timestamp), Price: price, Amount: newAmount, @@ -1510,6 +1545,12 @@ func (b *Bitfinex) WsInsertSnapshot(p currency.Pair, assetType asset.Item, books // WsUpdateOrderbook updates the orderbook list, removing and adding to the // orderbook sides func (b *Bitfinex) WsUpdateOrderbook(c *subscription.Subscription, p currency.Pair, assetType asset.Item, book []WebsocketBook, sequenceNo int64, fundingRate bool) error { + if c == nil { + return fmt.Errorf("%w: Subscription param", common.ErrNilPointer) + } + if len(c.Pairs) != 1 { + return subscription.ErrNotSinglePair + } orderbookUpdate := orderbook.Update{ Asset: assetType, Pair: p, @@ -1592,7 +1633,9 @@ func (b *Bitfinex) WsUpdateOrderbook(c *subscription.Subscription, p currency.Pa if err = validateCRC32(ob, checkme.Token); err != nil { log.Errorf(log.WebsocketMgr, "%s websocket orderbook update error, will resubscribe orderbook: %v", b.Name, err) - b.resubOrderbook(c) + if e2 := b.resubOrderbook(c); e2 != nil { + log.Errorf(log.WebsocketMgr, "%s error resubscribing orderbook: %v", b.Name, e2) + } return err } } @@ -1603,8 +1646,15 @@ func (b *Bitfinex) WsUpdateOrderbook(c *subscription.Subscription, p currency.Pa // resubOrderbook resubscribes the orderbook after a consistency error, probably a failed checksum, // which forces a fresh snapshot. If we don't do this the orderbook will keep erroring and drifting. // Flushing the orderbook happens immediately, but the ReSub itself is a go routine to avoid blocking the WS data channel -func (b *Bitfinex) resubOrderbook(c *subscription.Subscription) { - if err := b.Websocket.Orderbook.FlushOrderbook(c.Pair, c.Asset); err != nil { +func (b *Bitfinex) resubOrderbook(c *subscription.Subscription) error { + if c == nil { + return fmt.Errorf("%w: Subscription param", common.ErrNilPointer) + } + if len(c.Pairs) != 1 { + return subscription.ErrNotSinglePair + } + if err := b.Websocket.Orderbook.FlushOrderbook(c.Pairs[0], c.Asset); err != nil { + // Non-fatal error log.Errorf(log.ExchangeSys, "%s error flushing orderbook: %v", b.Name, err) } @@ -1614,13 +1664,15 @@ func (b *Bitfinex) resubOrderbook(c *subscription.Subscription) { log.Errorf(log.ExchangeSys, "%s error resubscribing orderbook: %v", b.Name, err) } }() + + return nil } // GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions() -func (b *Bitfinex) GenerateDefaultSubscriptions() ([]subscription.Subscription, error) { +func (b *Bitfinex) GenerateDefaultSubscriptions() (subscription.List, error) { var channels = []string{wsBook, wsTrades, wsTicker, wsCandles} - var subscriptions []subscription.Subscription + var subscriptions subscription.List assets := b.GetAssetTypes(true) for i := range assets { if !b.IsAssetWebsocketSupported(assets[i]) { @@ -1643,9 +1695,9 @@ func (b *Bitfinex) GenerateDefaultSubscriptions() ([]subscription.Subscription, params[CandlesPeriodKey] = "30" } - subscriptions = append(subscriptions, subscription.Subscription{ + subscriptions = append(subscriptions, &subscription.Subscription{ Channel: channels[j], - Pair: enabledPairs[k], + Pairs: currency.Pairs{enabledPairs[k]}, Params: params, Asset: assets[i], }) @@ -1665,26 +1717,26 @@ func (b *Bitfinex) ConfigureWS() error { } // Subscribe sends a websocket message to receive data from channels -func (b *Bitfinex) Subscribe(channels []subscription.Subscription) error { +func (b *Bitfinex) Subscribe(channels subscription.List) error { return b.ParallelChanOp(channels, b.subscribeToChan, 1) } // Unsubscribe sends a websocket message to stop receiving data from channels -func (b *Bitfinex) Unsubscribe(channels []subscription.Subscription) error { +func (b *Bitfinex) Unsubscribe(channels subscription.List) error { return b.ParallelChanOp(channels, b.unsubscribeFromChan, 1) } // subscribeToChan handles a single subscription and parses the result // on success it adds the subscription to the websocket -func (b *Bitfinex) subscribeToChan(chans []subscription.Subscription) error { +func (b *Bitfinex) subscribeToChan(chans subscription.List) error { if len(chans) != 1 { return errors.New("subscription batching limited to 1") } c := chans[0] - req, err := subscribeReq(&c) + req, err := subscribeReq(c) if err != nil { - return fmt.Errorf("%w: %w; Channel: %s Pair: %s", stream.ErrSubscriptionFailure, err, c.Channel, c.Pair) + return fmt.Errorf("%w: %w; Channel: %s Pair: %s", stream.ErrSubscriptionFailure, err, c.Channel, c.Pairs) } // subId is a single round-trip identifier that provides linking sub requests to chanIDs @@ -1695,23 +1747,22 @@ func (b *Bitfinex) subscribeToChan(chans []subscription.Subscription) error { // Add a temporary Key so we can find this Sub when we get the resp without delay or context switch // Otherwise we might drop the first messages after the subscribed resp c.Key = subID // Note subID string type avoids conflicts with later chanID key - - c.State = subscription.SubscribingState - err = b.Websocket.AddSubscription(&c) - if err != nil { - return fmt.Errorf("%w Channel: %s Pair: %s Error: %w", stream.ErrSubscriptionFailure, c.Channel, c.Pair, err) + if err = b.Websocket.AddSubscriptions(c); err != nil { + return fmt.Errorf("%w Channel: %s Pair: %s Error: %w", stream.ErrSubscriptionFailure, c.Channel, c.Pairs, err) } // Always remove the temporary subscription keyed by subID - defer b.Websocket.RemoveSubscriptions(c) + defer func() { + _ = b.Websocket.RemoveSubscriptions(c) + }() respRaw, err := b.Websocket.Conn.SendMessageReturnResponse("subscribe:"+subID, req) if err != nil { - return fmt.Errorf("%w: %w; Channel: %s Pair: %s", stream.ErrSubscriptionFailure, err, c.Channel, c.Pair) + return fmt.Errorf("%w: %w; Channel: %s Pair: %s", stream.ErrSubscriptionFailure, err, c.Channel, c.Pairs) } if err = b.getErrResp(respRaw); err != nil { - wErr := fmt.Errorf("%w: %w; Channel: %s Pair: %s", stream.ErrSubscriptionFailure, err, c.Channel, c.Pair) + wErr := fmt.Errorf("%w: %w; Channel: %s Pair: %s", stream.ErrSubscriptionFailure, err, c.Channel, c.Pairs) b.Websocket.DataHandler <- wErr return wErr } @@ -1721,6 +1772,13 @@ func (b *Bitfinex) subscribeToChan(chans []subscription.Subscription) error { // subscribeReq returns a map of request params for subscriptions func subscribeReq(c *subscription.Subscription) (map[string]interface{}, error) { + if c == nil { + return nil, fmt.Errorf("%w: Subscription param", common.ErrNilPointer) + } + if len(c.Pairs) != 1 { + return nil, subscription.ErrNotSinglePair + } + pair := c.Pairs[0] req := map[string]interface{}{ "event": "subscribe", "channel": c.Channel, @@ -1743,13 +1801,13 @@ func subscribeReq(c *subscription.Subscription) (map[string]interface{}, error) prefix = "f" } - needsDelimiter := c.Pair.Len() > 6 + needsDelimiter := pair.Len() > 6 var formattedPair string if needsDelimiter { - formattedPair = c.Pair.Format(currency.PairFormat{Uppercase: true, Delimiter: ":"}).String() + formattedPair = pair.Format(currency.PairFormat{Uppercase: true, Delimiter: ":"}).String() } else { - formattedPair = currency.PairFormat{Uppercase: true}.Format(c.Pair) + formattedPair = currency.PairFormat{Uppercase: true}.Format(pair) } if c.Channel == wsCandles { @@ -1776,7 +1834,7 @@ func subscribeReq(c *subscription.Subscription) (map[string]interface{}, error) } // unsubscribeFromChan sends a websocket message to stop receiving data from a channel -func (b *Bitfinex) unsubscribeFromChan(chans []subscription.Subscription) error { +func (b *Bitfinex) unsubscribeFromChan(chans subscription.List) error { if len(chans) != 1 { return errors.New("subscription batching limited to 1") } @@ -1802,9 +1860,7 @@ func (b *Bitfinex) unsubscribeFromChan(chans []subscription.Subscription) error return wErr } - b.Websocket.RemoveSubscriptions(c) - - return nil + return b.Websocket.RemoveSubscriptions(c) } // getErrResp takes a json response string and looks for an error event type diff --git a/exchanges/bitfinex/bitfinex_wrapper.go b/exchanges/bitfinex/bitfinex_wrapper.go index 41e21140c3b..3d676dcce2d 100644 --- a/exchanges/bitfinex/bitfinex_wrapper.go +++ b/exchanges/bitfinex/bitfinex_wrapper.go @@ -620,8 +620,8 @@ func (b *Bitfinex) SubmitOrder(ctx context.Context, o *order.Submit) (*order.Sub var orderID string status := order.New if b.Websocket.CanUseAuthenticatedWebsocketForWrapper() { - symbolStr, err := b.fixCasing(fPair, o.AssetType) //nolint:govet // intentional shadow of err - if err != nil { + var symbolStr string + if symbolStr, err = b.fixCasing(fPair, o.AssetType); err != nil { return nil, err } orderType := strings.ToUpper(o.Type.String()) diff --git a/exchanges/bithumb/bithumb_websocket.go b/exchanges/bithumb/bithumb_websocket.go index 667ce131c71..929d68a61e2 100644 --- a/exchanges/bithumb/bithumb_websocket.go +++ b/exchanges/bithumb/bithumb_websocket.go @@ -7,6 +7,7 @@ import ( "time" "github.com/gorilla/websocket" + "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/stream" "github.com/thrasher-corp/gocryptotrader/exchanges/subscription" @@ -166,10 +167,10 @@ func (b *Bithumb) wsHandleData(respRaw []byte) error { return nil } -// GenerateSubscriptions generates the default subscription set -func (b *Bithumb) GenerateSubscriptions() ([]subscription.Subscription, error) { +// generateSubscriptions generates the default subscription set +func (b *Bithumb) generateSubscriptions() (subscription.List, error) { var channels = []string{"ticker", "transaction", "orderbookdepth"} - var subscriptions []subscription.Subscription + var subscriptions subscription.List pairs, err := b.GetEnabledPairs(asset.Spot) if err != nil { return nil, err @@ -179,44 +180,36 @@ func (b *Bithumb) GenerateSubscriptions() ([]subscription.Subscription, error) { if err != nil { return nil, err } - - for x := range pairs { - for y := range channels { - subscriptions = append(subscriptions, subscription.Subscription{ - Channel: channels[y], - Pair: pairs[x].Format(pFmt), - Asset: asset.Spot, - }) - } + pairs = pairs.Format(pFmt) + + for y := range channels { + subscriptions = append(subscriptions, &subscription.Subscription{ + Channel: channels[y], + Pairs: pairs, + Asset: asset.Spot, + }) } return subscriptions, nil } // Subscribe subscribes to a set of channels -func (b *Bithumb) Subscribe(channelsToSubscribe []subscription.Subscription) error { - subs := make(map[string]*WsSubscribe) - for i := range channelsToSubscribe { - s, ok := subs[channelsToSubscribe[i].Channel] - if !ok { - s = &WsSubscribe{ - Type: channelsToSubscribe[i].Channel, - } - subs[channelsToSubscribe[i].Channel] = s +func (b *Bithumb) Subscribe(channelsToSubscribe subscription.List) error { + var errs error + for _, s := range channelsToSubscribe { + req := &WsSubscribe{ + Type: s.Channel, + Symbols: s.Pairs, + } + if s.Channel == "ticker" { + req.TickTypes = wsDefaultTickTypes + } + err := b.Websocket.Conn.SendJSONMessage(req) + if err == nil { + err = b.Websocket.AddSuccessfulSubscriptions(s) } - s.Symbols = append(s.Symbols, channelsToSubscribe[i].Pair) - } - - tSub, ok := subs["ticker"] - if ok { - tSub.TickTypes = wsDefaultTickTypes - } - - for _, s := range subs { - err := b.Websocket.Conn.SendJSONMessage(s) if err != nil { - return err + errs = common.AppendError(errs, err) } } - b.Websocket.AddSuccessfulSubscriptions(channelsToSubscribe...) - return nil + return errs } diff --git a/exchanges/bithumb/bithumb_websocket_test.go b/exchanges/bithumb/bithumb_websocket_test.go index f5eeabf9480..1a7dcac2481 100644 --- a/exchanges/bithumb/bithumb_websocket_test.go +++ b/exchanges/bithumb/bithumb_websocket_test.go @@ -2,7 +2,6 @@ package bithumb import ( "errors" - "sync" "testing" "github.com/thrasher-corp/gocryptotrader/currency" @@ -46,7 +45,6 @@ func TestWsHandleData(t *testing.T) { }, }, Websocket: &stream.Websocket{ - Wg: new(sync.WaitGroup), DataHandler: make(chan interface{}, 1), }, }, @@ -91,7 +89,7 @@ func TestWsHandleData(t *testing.T) { func TestGenerateSubscriptions(t *testing.T) { t.Parallel() - sub, err := b.GenerateSubscriptions() + sub, err := b.generateSubscriptions() if err != nil { t.Fatal(err) } diff --git a/exchanges/bithumb/bithumb_wrapper.go b/exchanges/bithumb/bithumb_wrapper.go index 4b0dec3325b..2b25583d7e7 100644 --- a/exchanges/bithumb/bithumb_wrapper.go +++ b/exchanges/bithumb/bithumb_wrapper.go @@ -162,7 +162,7 @@ func (b *Bithumb) Setup(exch *config.Exchange) error { RunningURL: ePoint, Connector: b.WsConnect, Subscriber: b.Subscribe, - GenerateSubscriptions: b.GenerateSubscriptions, + GenerateSubscriptions: b.generateSubscriptions, Features: &b.Features.Supports.WebsocketCapabilities, }) if err != nil { diff --git a/exchanges/bitmex/bitmex_websocket.go b/exchanges/bitmex/bitmex_websocket.go index 7a2e1167679..6779a97a1e9 100644 --- a/exchanges/bitmex/bitmex_websocket.go +++ b/exchanges/bitmex/bitmex_websocket.go @@ -65,6 +65,11 @@ const ( bitmexActionUpdateData = "update" ) +var subscriptionNames = map[string]string{ + subscription.OrderbookChannel: bitmexWSOrderbookL2, + subscription.AllTradesChannel: bitmexWSTrade, +} + // WsConnect initiates a new websocket connection func (b *Bitmex) WsConnect() error { if !b.Websocket.IsEnabled() || !b.IsEnabled() { @@ -100,18 +105,11 @@ func (b *Bitmex) WsConnect() error { if b.Websocket.CanUseAuthenticatedEndpoints() { err = b.websocketSendAuth(context.TODO()) if err != nil { - log.Errorf(log.ExchangeSys, - "%v - authentication failed: %v\n", - b.Name, - err) - } else { - authsubs, err := b.GenerateAuthenticatedSubscriptions() - if err != nil { - return err - } - return b.Websocket.SubscribeToChannels(authsubs) + b.Websocket.SetCanUseAuthenticatedEndpoints(false) + log.Errorf(log.ExchangeSys, "%v - authentication failed: %v\n", b.Name, err) } } + return nil } @@ -544,122 +542,96 @@ func (b *Bitmex) processOrderbook(data []OrderBookL2, action string, p currency. return nil } -// GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions() -func (b *Bitmex) GenerateDefaultSubscriptions() ([]subscription.Subscription, error) { - channels := []string{bitmexWSOrderbookL2, bitmexWSTrade} - subscriptions := []subscription.Subscription{ - { - Channel: bitmexWSAnnouncement, - }, - } +// generateSubscriptions returns Adds default subscriptions to websocket to be handled by ManageSubscriptions() +func (b *Bitmex) generateSubscriptions() (subscription.List, error) { + authed := b.Websocket.CanUseAuthenticatedEndpoints() - assets := b.GetAssetTypes(true) - for x := range assets { - pFmt, err := b.GetPairFormat(assets[x], true) + assetPairs := map[asset.Item]currency.Pairs{} + for _, a := range b.GetAssetTypes(true) { + p, err := b.GetEnabledPairs(a) if err != nil { return nil, err } - contracts, err := b.GetEnabledPairs(assets[x]) + f, err := b.GetPairFormat(a, true) if err != nil { return nil, err } - for y := range contracts { - for z := range channels { - if assets[x] == asset.Index && channels[z] == bitmexWSOrderbookL2 { - // There are no L2 orderbook for index assets - continue - } - subscriptions = append(subscriptions, subscription.Subscription{ - Channel: channels[z] + ":" + pFmt.Format(contracts[y]), - Pair: contracts[y], - Asset: assets[x], - }) - } - } + assetPairs[a] = p.Format(f) } - return subscriptions, nil -} -// GenerateAuthenticatedSubscriptions Adds authenticated subscriptions to websocket to be handled by ManageSubscriptions() -func (b *Bitmex) GenerateAuthenticatedSubscriptions() ([]subscription.Subscription, error) { - if !b.Websocket.CanUseAuthenticatedEndpoints() { - return nil, nil - } + subs := subscription.List{} + for _, baseSub := range b.Features.Subscriptions { + if !authed && baseSub.Authenticated { + continue + } - pFmt, err := b.GetPairFormat(asset.PerpetualContract, true) - if err != nil { - return nil, err - } - contracts, err := b.GetEnabledPairs(asset.PerpetualContract) - if err != nil { - return nil, err - } - channels := []string{bitmexWSExecution, - bitmexWSPosition, - } - subscriptions := []subscription.Subscription{ - { - Channel: bitmexWSAffiliate, - }, - { - Channel: bitmexWSOrder, - }, - { - Channel: bitmexWSMargin, - }, - { - Channel: bitmexWSPrivateNotifications, - }, - { - Channel: bitmexWSTransact, - }, - { - Channel: bitmexWSWallet, - }, - } - for i := range channels { - for j := range contracts { - subscriptions = append(subscriptions, subscription.Subscription{ - Channel: channels[i] + ":" + pFmt.Format(contracts[j]), - Pair: contracts[j], - Asset: asset.PerpetualContract, - }) + if baseSub.Asset == asset.Empty { + // Skip pair handling for subs which don't have an asset + subs = append(subs, baseSub.Clone()) + continue + } + + for a, p := range assetPairs { + if baseSub.Channel == bitmexWSOrderbookL2 && a == asset.Index { + continue // There are no L2 orderbook for index assets + } + if baseSub.Asset != asset.All && baseSub.Asset != a { + continue + } + s := baseSub.Clone() + s.Asset = a + s.Pairs = p + subs = append(subs, s) } } - return subscriptions, nil + + return subs, nil } // Subscribe subscribes to a websocket channel -func (b *Bitmex) Subscribe(channelsToSubscribe []subscription.Subscription) error { - var subscriber WebsocketRequest - subscriber.Command = "subscribe" - for i := range channelsToSubscribe { - subscriber.Arguments = append(subscriber.Arguments, - channelsToSubscribe[i].Channel) +func (b *Bitmex) Subscribe(subs subscription.List) error { + req := WebsocketRequest{ + Command: "subscribe", } - err := b.Websocket.Conn.SendJSONMessage(subscriber) - if err != nil { - return err + for _, s := range subs { + for _, p := range s.Pairs { + cName := channelName(s.Channel) + req.Arguments = append(req.Arguments, cName+":"+p.String()) + } } - b.Websocket.AddSuccessfulSubscriptions(channelsToSubscribe...) - return nil + err := b.Websocket.Conn.SendJSONMessage(req) + if err == nil { + err = b.Websocket.AddSuccessfulSubscriptions(subs...) + } + return err } // Unsubscribe sends a websocket message to stop receiving data from the channel -func (b *Bitmex) Unsubscribe(channelsToUnsubscribe []subscription.Subscription) error { - var unsubscriber WebsocketRequest - unsubscriber.Command = "unsubscribe" +func (b *Bitmex) Unsubscribe(subs subscription.List) error { + req := WebsocketRequest{ + Command: "unsubscribe", + } - for i := range channelsToUnsubscribe { - unsubscriber.Arguments = append(unsubscriber.Arguments, - channelsToUnsubscribe[i].Channel) + for _, s := range subs { + for _, p := range s.Pairs { + cName := channelName(s.Channel) + req.Arguments = append(req.Arguments, cName+":"+p.String()) + } } - err := b.Websocket.Conn.SendJSONMessage(unsubscriber) - if err != nil { - return err + err := b.Websocket.Conn.SendJSONMessage(req) + if err == nil { + err = b.Websocket.RemoveSubscriptions(subs...) } - b.Websocket.RemoveSubscriptions(channelsToUnsubscribe...) - return nil + return err +} + +// channelName converts global channel Names used in config of channel input into bitmex channel names +// returns the name unchanged if no match is found +func channelName(name string) string { + if s, ok := subscriptionNames[name]; ok { + return s + } + return name } // WebsocketSendAuth sends an authenticated subscription diff --git a/exchanges/bitmex/bitmex_wrapper.go b/exchanges/bitmex/bitmex_wrapper.go index b5f53e03eb2..b980463bbea 100644 --- a/exchanges/bitmex/bitmex_wrapper.go +++ b/exchanges/bitmex/bitmex_wrapper.go @@ -28,6 +28,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/request" "github.com/thrasher-corp/gocryptotrader/exchanges/stream" "github.com/thrasher-corp/gocryptotrader/exchanges/stream/buffer" + "github.com/thrasher-corp/gocryptotrader/exchanges/subscription" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" "github.com/thrasher-corp/gocryptotrader/exchanges/trade" "github.com/thrasher-corp/gocryptotrader/log" @@ -136,6 +137,19 @@ func (b *Bitmex) SetDefaults() { Enabled: exchange.FeaturesEnabled{ AutoPairUpdates: true, }, + Subscriptions: subscription.List{ + {Enabled: true, Channel: bitmexWSAnnouncement}, + {Enabled: true, Channel: bitmexWSOrderbookL2, Asset: asset.All}, + {Enabled: true, Channel: bitmexWSTrade, Asset: asset.All}, + {Enabled: true, Channel: bitmexWSAffiliate, Authenticated: true}, + {Enabled: true, Channel: bitmexWSOrder, Authenticated: true}, + {Enabled: true, Channel: bitmexWSMargin, Authenticated: true}, + {Enabled: true, Channel: bitmexWSPrivateNotifications, Authenticated: true}, + {Enabled: true, Channel: bitmexWSTransact, Authenticated: true}, + {Enabled: true, Channel: bitmexWSWallet, Authenticated: true}, + {Enabled: true, Channel: bitmexWSExecution, Authenticated: true, Asset: asset.PerpetualContract}, + {Enabled: true, Channel: bitmexWSPosition, Authenticated: true, Asset: asset.PerpetualContract}, + }, } b.Requester, err = request.New(b.Name, @@ -185,7 +199,7 @@ func (b *Bitmex) Setup(exch *config.Exchange) error { Connector: b.WsConnect, Subscriber: b.Subscribe, Unsubscriber: b.Unsubscribe, - GenerateSubscriptions: b.GenerateDefaultSubscriptions, + GenerateSubscriptions: b.generateSubscriptions, Features: &b.Features.Supports.WebsocketCapabilities, OrderbookBufferConfig: buffer.Config{ UpdateEntriesByID: true, diff --git a/exchanges/bitstamp/bitstamp_websocket.go b/exchanges/bitstamp/bitstamp_websocket.go index 22c88329ee8..ab887e422a3 100644 --- a/exchanges/bitstamp/bitstamp_websocket.go +++ b/exchanges/bitstamp/bitstamp_websocket.go @@ -231,30 +231,30 @@ func (b *Bitstamp) handleWSOrder(wsResp *websocketResponse, msg []byte) error { return nil } -func (b *Bitstamp) generateDefaultSubscriptions() ([]subscription.Subscription, error) { +func (b *Bitstamp) generateDefaultSubscriptions() (subscription.List, error) { enabledCurrencies, err := b.GetEnabledPairs(asset.Spot) if err != nil { return nil, err } - var subscriptions []subscription.Subscription + var subscriptions subscription.List for i := range enabledCurrencies { p, err := b.FormatExchangeCurrency(enabledCurrencies[i], asset.Spot) if err != nil { return nil, err } for j := range defaultSubChannels { - subscriptions = append(subscriptions, subscription.Subscription{ + subscriptions = append(subscriptions, &subscription.Subscription{ Channel: defaultSubChannels[j] + "_" + p.String(), Asset: asset.Spot, - Pair: p, + Pairs: currency.Pairs{p}, }) } if b.Websocket.CanUseAuthenticatedEndpoints() { for j := range defaultAuthSubChannels { - subscriptions = append(subscriptions, subscription.Subscription{ + subscriptions = append(subscriptions, &subscription.Subscription{ Channel: defaultAuthSubChannels[j] + "_" + p.String(), Asset: asset.Spot, - Pair: p, + Pairs: currency.Pairs{p}, Params: map[string]interface{}{ "auth": struct{}{}, }, @@ -266,7 +266,7 @@ func (b *Bitstamp) generateDefaultSubscriptions() ([]subscription.Subscription, } // Subscribe sends a websocket message to receive data from the channel -func (b *Bitstamp) Subscribe(channelsToSubscribe []subscription.Subscription) error { +func (b *Bitstamp) Subscribe(channelsToSubscribe subscription.List) error { var errs error var auth *WebsocketAuthResponse @@ -281,44 +281,46 @@ func (b *Bitstamp) Subscribe(channelsToSubscribe []subscription.Subscription) er } } - for i := range channelsToSubscribe { + for _, s := range channelsToSubscribe { req := websocketEventRequest{ Event: "bts:subscribe", Data: websocketData{ - Channel: channelsToSubscribe[i].Channel, + Channel: s.Channel, }, } - if _, ok := channelsToSubscribe[i].Params["auth"]; ok && auth != nil { + if _, ok := s.Params["auth"]; ok && auth != nil { req.Data.Channel = "private-" + req.Data.Channel + "-" + strconv.Itoa(int(auth.UserID)) req.Data.Auth = auth.Token } err := b.Websocket.Conn.SendJSONMessage(req) + if err == nil { + err = b.Websocket.AddSuccessfulSubscriptions(s) + } if err != nil { errs = common.AppendError(errs, err) - continue } - b.Websocket.AddSuccessfulSubscriptions(channelsToSubscribe[i]) } return errs } // Unsubscribe sends a websocket message to stop receiving data from the channel -func (b *Bitstamp) Unsubscribe(channelsToUnsubscribe []subscription.Subscription) error { +func (b *Bitstamp) Unsubscribe(channelsToUnsubscribe subscription.List) error { var errs error - for i := range channelsToUnsubscribe { + for _, s := range channelsToUnsubscribe { req := websocketEventRequest{ Event: "bts:unsubscribe", Data: websocketData{ - Channel: channelsToUnsubscribe[i].Channel, + Channel: s.Channel, }, } err := b.Websocket.Conn.SendJSONMessage(req) + if err == nil { + err = b.Websocket.RemoveSubscriptions(s) + } if err != nil { errs = common.AppendError(errs, err) - continue } - b.Websocket.RemoveSubscriptions(channelsToUnsubscribe[i]) } return errs } diff --git a/exchanges/btcmarkets/btcmarkets.go b/exchanges/btcmarkets/btcmarkets.go index 38d91d8d04e..b8425d4814c 100644 --- a/exchanges/btcmarkets/btcmarkets.go +++ b/exchanges/btcmarkets/btcmarkets.go @@ -82,13 +82,13 @@ const ( immediateOrCancel = "IOC" fillOrKill = "FOK" - subscribe = "subscribe" - fundChange = "fundChange" - orderChange = "orderChange" - heartbeat = "heartbeat" - tick = "tick" - wsOB = "orderbookUpdate" - tradeEndPoint = "trade" + subscribe = "subscribe" + fundChange = "fundChange" + orderChange = "orderChange" + heartbeat = "heartbeat" + tick = "tick" + wsOrderbookUpdate = "orderbookUpdate" + tradeEndPoint = "trade" // Subscription management when connection and subscription established addSubscription = "addSubscription" diff --git a/exchanges/btcmarkets/btcmarkets_websocket.go b/exchanges/btcmarkets/btcmarkets_websocket.go index c931f6b51bb..18a715ae848 100644 --- a/exchanges/btcmarkets/btcmarkets_websocket.go +++ b/exchanges/btcmarkets/btcmarkets_websocket.go @@ -127,7 +127,7 @@ func (b *BTCMarkets) wsHandleData(respRaw []byte) error { if b.Verbose { log.Debugf(log.ExchangeSys, "%v - Websocket heartbeat received %s", b.Name, respRaw) } - case wsOB: + case wsOrderbookUpdate: var ob WsOrderbook err := json.Unmarshal(respRaw, &ob) if err != nil { @@ -325,26 +325,24 @@ func (b *BTCMarkets) wsHandleData(respRaw []byte) error { return nil } -func (b *BTCMarkets) generateDefaultSubscriptions() ([]subscription.Subscription, error) { - var channels = []string{wsOB, tick, tradeEndPoint} +func (b *BTCMarkets) generateDefaultSubscriptions() (subscription.List, error) { + var channels = []string{wsOrderbookUpdate, tick, tradeEndPoint} enabledCurrencies, err := b.GetEnabledPairs(asset.Spot) if err != nil { return nil, err } - var subscriptions []subscription.Subscription + var subscriptions subscription.List for i := range channels { - for j := range enabledCurrencies { - subscriptions = append(subscriptions, subscription.Subscription{ - Channel: channels[i], - Pair: enabledCurrencies[j], - Asset: asset.Spot, - }) - } + subscriptions = append(subscriptions, &subscription.Subscription{ + Channel: channels[i], + Pairs: enabledCurrencies, + Asset: asset.Spot, + }) } if b.Websocket.CanUseAuthenticatedEndpoints() { for i := range authChannels { - subscriptions = append(subscriptions, subscription.Subscription{ + subscriptions = append(subscriptions, &subscription.Subscription{ Channel: authChannels[i], }) } @@ -353,96 +351,91 @@ func (b *BTCMarkets) generateDefaultSubscriptions() ([]subscription.Subscription } // Subscribe sends a websocket message to receive data from the channel -func (b *BTCMarkets) Subscribe(subs []subscription.Subscription) error { - var payload WsSubscribe - if len(subs) > 1 { - // TODO: Expand this to stream package as this assumes that we are doing - // an initial sync. - payload.MessageType = subscribe - } else { - payload.MessageType = addSubscription - payload.ClientType = clientType +func (b *BTCMarkets) Subscribe(subs subscription.List) error { + baseReq := &WsSubscribe{ + MessageType: subscribe, } - var authenticate bool - for i := range subs { - if !authenticate && common.StringDataContains(authChannels, subs[i].Channel) { - authenticate = true - } - payload.Channels = append(payload.Channels, subs[i].Channel) - if subs[i].Pair.IsEmpty() { - continue + var errs error + for _, s := range subs { + if baseReq.Key == "" && common.StringDataContains(authChannels, s.Channel) { + if err := b.authWsSubscibeReq(baseReq); err != nil { + return err + } } - pair := subs[i].Pair.String() - if common.StringDataCompare(payload.MarketIDs, pair) { - continue + + if baseReq.MessageType == subscribe && len(b.Websocket.GetSubscriptions()) != 0 { + baseReq.MessageType = addSubscription // After first *successful* subscription API requires addSubscription + baseReq.ClientType = clientType // Note: Only addSubscription requires/accepts clientType } - payload.MarketIDs = append(payload.MarketIDs, pair) - } - if authenticate { - creds, err := b.GetCredentials(context.TODO()) - if err != nil { - return err + r := baseReq + + r.Channels = []string{s.Channel} + r.MarketIDs = s.Pairs.Strings() + + err := b.Websocket.Conn.SendJSONMessage(r) + if err == nil { + err = b.Websocket.AddSuccessfulSubscriptions(s) } - signTime := strconv.FormatInt(time.Now().UnixMilli(), 10) - strToSign := "/users/self/subscribe" + "\n" + signTime - var tempSign []byte - tempSign, err = crypto.GetHMAC(crypto.HashSHA512, - []byte(strToSign), - []byte(creds.Secret)) if err != nil { - return err + errs = common.AppendError(errs, err) } - sign := crypto.Base64Encode(tempSign) - payload.Key = creds.Key - payload.Signature = sign - payload.Timestamp = signTime } - if err := b.Websocket.Conn.SendJSONMessage(payload); err != nil { + return errs +} + +func (b *BTCMarkets) authWsSubscibeReq(r *WsSubscribe) error { + creds, err := b.GetCredentials(context.TODO()) + if err != nil { return err } - b.Websocket.AddSuccessfulSubscriptions(subs...) + r.Timestamp = strconv.FormatInt(time.Now().UnixMilli(), 10) + strToSign := "/users/self/subscribe" + "\n" + r.Timestamp + tempSign, err := crypto.GetHMAC(crypto.HashSHA512, []byte(strToSign), []byte(creds.Secret)) + if err != nil { + return err + } + sign := crypto.Base64Encode(tempSign) + r.Key = creds.Key + r.Signature = sign return nil } // Unsubscribe sends a websocket message to manage and remove a subscription. -func (b *BTCMarkets) Unsubscribe(subs []subscription.Subscription) error { - payload := WsSubscribe{ - MessageType: removeSubscription, - ClientType: clientType, - } - for i := range subs { - payload.Channels = append(payload.Channels, subs[i].Channel) - if subs[i].Pair.IsEmpty() { - continue +func (b *BTCMarkets) Unsubscribe(subs subscription.List) error { + var errs error + for _, s := range subs { + req := WsSubscribe{ + MessageType: removeSubscription, + ClientType: clientType, + Channels: []string{s.Channel}, + MarketIDs: s.Pairs.Strings(), } - pair := subs[i].Pair.String() - if common.StringDataCompare(payload.MarketIDs, pair) { - continue + err := b.Websocket.Conn.SendJSONMessage(req) + if err == nil { + err = b.Websocket.RemoveSubscriptions(s) + } + if err != nil { + errs = common.AppendError(errs, err) } - payload.MarketIDs = append(payload.MarketIDs, pair) - } - - err := b.Websocket.Conn.SendJSONMessage(payload) - if err != nil { - return err } - b.Websocket.RemoveSubscriptions(subs...) - return nil + return errs } // ReSubscribeSpecificOrderbook removes the subscription and the subscribes // again to fetch a new snapshot in the event of a de-sync event. func (b *BTCMarkets) ReSubscribeSpecificOrderbook(pair currency.Pair) error { - sub := []subscription.Subscription{{ - Channel: wsOB, - Pair: pair, + sub := subscription.List{{ + Channel: wsOrderbookUpdate, + Pairs: currency.Pairs{pair}, Asset: asset.Spot, }} - if err := b.Unsubscribe(sub); err != nil { + if err := b.Unsubscribe(sub); err != nil && !errors.Is(err, subscription.ErrNotFound) { + // ErrNotFound is okay, because we might be re-subscribing a single pair from a larger list + // BTC-Market handles unsub/sub of one pair gracefully and the other pairs are unaffected return err } return b.Subscribe(sub) diff --git a/exchanges/btse/btse_websocket.go b/exchanges/btse/btse_websocket.go index 936d5af2fe0..4bac49517bb 100644 --- a/exchanges/btse/btse_websocket.go +++ b/exchanges/btse/btse_websocket.go @@ -361,23 +361,23 @@ func (b *BTSE) orderbookFilter(price, amount float64) bool { } // GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions() -func (b *BTSE) GenerateDefaultSubscriptions() ([]subscription.Subscription, error) { +func (b *BTSE) GenerateDefaultSubscriptions() (subscription.List, error) { var channels = []string{"orderBookL2Api:%s_0", "tradeHistory:%s"} pairs, err := b.GetEnabledPairs(asset.Spot) if err != nil { return nil, err } - var subscriptions []subscription.Subscription + var subscriptions subscription.List if b.Websocket.CanUseAuthenticatedEndpoints() { - subscriptions = append(subscriptions, subscription.Subscription{ + subscriptions = append(subscriptions, &subscription.Subscription{ Channel: "notificationApi", }) } for i := range channels { for j := range pairs { - subscriptions = append(subscriptions, subscription.Subscription{ + subscriptions = append(subscriptions, &subscription.Subscription{ Channel: fmt.Sprintf(channels[i], pairs[j]), - Pair: pairs[j], + Pairs: currency.Pairs{pairs[j]}, Asset: asset.Spot, }) } @@ -386,22 +386,21 @@ func (b *BTSE) GenerateDefaultSubscriptions() ([]subscription.Subscription, erro } // Subscribe sends a websocket message to receive data from the channel -func (b *BTSE) Subscribe(channelsToSubscribe []subscription.Subscription) error { +func (b *BTSE) Subscribe(channelsToSubscribe subscription.List) error { var sub wsSub sub.Operation = "subscribe" for i := range channelsToSubscribe { sub.Arguments = append(sub.Arguments, channelsToSubscribe[i].Channel) } err := b.Websocket.Conn.SendJSONMessage(sub) - if err != nil { - return err + if err == nil { + err = b.Websocket.AddSuccessfulSubscriptions(channelsToSubscribe...) } - b.Websocket.AddSuccessfulSubscriptions(channelsToSubscribe...) - return nil + return err } // Unsubscribe sends a websocket message to stop receiving data from the channel -func (b *BTSE) Unsubscribe(channelsToUnsubscribe []subscription.Subscription) error { +func (b *BTSE) Unsubscribe(channelsToUnsubscribe subscription.List) error { var unSub wsSub unSub.Operation = "unsubscribe" for i := range channelsToUnsubscribe { @@ -409,9 +408,8 @@ func (b *BTSE) Unsubscribe(channelsToUnsubscribe []subscription.Subscription) er channelsToUnsubscribe[i].Channel) } err := b.Websocket.Conn.SendJSONMessage(unSub) - if err != nil { - return err + if err == nil { + err = b.Websocket.RemoveSubscriptions(channelsToUnsubscribe...) } - b.Websocket.RemoveSubscriptions(channelsToUnsubscribe...) - return nil + return err } diff --git a/exchanges/bybit/bybit_inverse_websocket.go b/exchanges/bybit/bybit_inverse_websocket.go index d1387c277f8..60f8981c1bf 100644 --- a/exchanges/bybit/bybit_inverse_websocket.go +++ b/exchanges/bybit/bybit_inverse_websocket.go @@ -4,6 +4,7 @@ import ( "net/http" "github.com/gorilla/websocket" + "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/stream" "github.com/thrasher-corp/gocryptotrader/exchanges/subscription" @@ -32,8 +33,8 @@ func (by *Bybit) WsInverseConnect() error { } // GenerateInverseDefaultSubscriptions generates default subscription -func (by *Bybit) GenerateInverseDefaultSubscriptions() ([]subscription.Subscription, error) { - var subscriptions []subscription.Subscription +func (by *Bybit) GenerateInverseDefaultSubscriptions() (subscription.List, error) { + var subscriptions subscription.List var channels = []string{chanOrderbook, chanPublicTrade, chanPublicTicker} pairs, err := by.GetEnabledPairs(asset.CoinMarginedFutures) if err != nil { @@ -42,9 +43,9 @@ func (by *Bybit) GenerateInverseDefaultSubscriptions() ([]subscription.Subscript for z := range pairs { for x := range channels { subscriptions = append(subscriptions, - subscription.Subscription{ + &subscription.Subscription{ Channel: channels[x], - Pair: pairs[z], + Pairs: currency.Pairs{pairs[z]}, Asset: asset.CoinMarginedFutures, }) } @@ -53,16 +54,16 @@ func (by *Bybit) GenerateInverseDefaultSubscriptions() ([]subscription.Subscript } // InverseSubscribe sends a subscription message to linear public channels. -func (by *Bybit) InverseSubscribe(channelSubscriptions []subscription.Subscription) error { +func (by *Bybit) InverseSubscribe(channelSubscriptions subscription.List) error { return by.handleInversePayloadSubscription("subscribe", channelSubscriptions) } // InverseUnsubscribe sends an unsubscription messages through linear public channels. -func (by *Bybit) InverseUnsubscribe(channelSubscriptions []subscription.Subscription) error { +func (by *Bybit) InverseUnsubscribe(channelSubscriptions subscription.List) error { return by.handleInversePayloadSubscription("unsubscribe", channelSubscriptions) } -func (by *Bybit) handleInversePayloadSubscription(operation string, channelSubscriptions []subscription.Subscription) error { +func (by *Bybit) handleInversePayloadSubscription(operation string, channelSubscriptions subscription.List) error { payloads, err := by.handleSubscriptions(asset.CoinMarginedFutures, operation, channelSubscriptions) if err != nil { return err diff --git a/exchanges/bybit/bybit_linear_websocket.go b/exchanges/bybit/bybit_linear_websocket.go index 9b3ed08426a..a6ce8ade1dd 100644 --- a/exchanges/bybit/bybit_linear_websocket.go +++ b/exchanges/bybit/bybit_linear_websocket.go @@ -41,8 +41,8 @@ func (by *Bybit) WsLinearConnect() error { } // GenerateLinearDefaultSubscriptions generates default subscription -func (by *Bybit) GenerateLinearDefaultSubscriptions() ([]subscription.Subscription, error) { - var subscriptions []subscription.Subscription +func (by *Bybit) GenerateLinearDefaultSubscriptions() (subscription.List, error) { + var subscriptions subscription.List var channels = []string{chanOrderbook, chanPublicTrade, chanPublicTicker} pairs, err := by.GetEnabledPairs(asset.USDTMarginedFutures) if err != nil { @@ -61,9 +61,9 @@ func (by *Bybit) GenerateLinearDefaultSubscriptions() ([]subscription.Subscripti for p := range linearPairMap[a] { for x := range channels { subscriptions = append(subscriptions, - subscription.Subscription{ + &subscription.Subscription{ Channel: channels[x], - Pair: pairs[p], + Pairs: currency.Pairs{pairs[p]}, Asset: a, }) } @@ -73,16 +73,16 @@ func (by *Bybit) GenerateLinearDefaultSubscriptions() ([]subscription.Subscripti } // LinearSubscribe sends a subscription message to linear public channels. -func (by *Bybit) LinearSubscribe(channelSubscriptions []subscription.Subscription) error { +func (by *Bybit) LinearSubscribe(channelSubscriptions subscription.List) error { return by.handleLinearPayloadSubscription("subscribe", channelSubscriptions) } // LinearUnsubscribe sends an unsubscription messages through linear public channels. -func (by *Bybit) LinearUnsubscribe(channelSubscriptions []subscription.Subscription) error { +func (by *Bybit) LinearUnsubscribe(channelSubscriptions subscription.List) error { return by.handleLinearPayloadSubscription("unsubscribe", channelSubscriptions) } -func (by *Bybit) handleLinearPayloadSubscription(operation string, channelSubscriptions []subscription.Subscription) error { +func (by *Bybit) handleLinearPayloadSubscription(operation string, channelSubscriptions subscription.List) error { payloads, err := by.handleSubscriptions(asset.USDTMarginedFutures, operation, channelSubscriptions) if err != nil { return err diff --git a/exchanges/bybit/bybit_options_websocket.go b/exchanges/bybit/bybit_options_websocket.go index 4bb25cef2a9..72a86b0c9c6 100644 --- a/exchanges/bybit/bybit_options_websocket.go +++ b/exchanges/bybit/bybit_options_websocket.go @@ -6,6 +6,7 @@ import ( "strconv" "github.com/gorilla/websocket" + "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/stream" "github.com/thrasher-corp/gocryptotrader/exchanges/subscription" @@ -39,8 +40,8 @@ func (by *Bybit) WsOptionsConnect() error { } // GenerateOptionsDefaultSubscriptions generates default subscription -func (by *Bybit) GenerateOptionsDefaultSubscriptions() ([]subscription.Subscription, error) { - var subscriptions []subscription.Subscription +func (by *Bybit) GenerateOptionsDefaultSubscriptions() (subscription.List, error) { + var subscriptions subscription.List var channels = []string{chanOrderbook, chanPublicTrade, chanPublicTicker} pairs, err := by.GetEnabledPairs(asset.Options) if err != nil { @@ -49,9 +50,9 @@ func (by *Bybit) GenerateOptionsDefaultSubscriptions() ([]subscription.Subscript for z := range pairs { for x := range channels { subscriptions = append(subscriptions, - subscription.Subscription{ + &subscription.Subscription{ Channel: channels[x], - Pair: pairs[z], + Pairs: currency.Pairs{pairs[z]}, Asset: asset.Options, }) } @@ -60,16 +61,16 @@ func (by *Bybit) GenerateOptionsDefaultSubscriptions() ([]subscription.Subscript } // OptionSubscribe sends a subscription message to options public channels. -func (by *Bybit) OptionSubscribe(channelSubscriptions []subscription.Subscription) error { +func (by *Bybit) OptionSubscribe(channelSubscriptions subscription.List) error { return by.handleOptionsPayloadSubscription("subscribe", channelSubscriptions) } // OptionUnsubscribe sends an unsubscription messages through options public channels. -func (by *Bybit) OptionUnsubscribe(channelSubscriptions []subscription.Subscription) error { +func (by *Bybit) OptionUnsubscribe(channelSubscriptions subscription.List) error { return by.handleOptionsPayloadSubscription("unsubscribe", channelSubscriptions) } -func (by *Bybit) handleOptionsPayloadSubscription(operation string, channelSubscriptions []subscription.Subscription) error { +func (by *Bybit) handleOptionsPayloadSubscription(operation string, channelSubscriptions subscription.List) error { payloads, err := by.handleSubscriptions(asset.Options, operation, channelSubscriptions) if err != nil { return err diff --git a/exchanges/bybit/bybit_websocket.go b/exchanges/bybit/bybit_websocket.go index a4fa0476d5d..09f9c416d6e 100644 --- a/exchanges/bybit/bybit_websocket.go +++ b/exchanges/bybit/bybit_websocket.go @@ -134,11 +134,11 @@ func (by *Bybit) WsAuth(ctx context.Context) error { } // Subscribe sends a websocket message to receive data from the channel -func (by *Bybit) Subscribe(channelsToSubscribe []subscription.Subscription) error { +func (by *Bybit) Subscribe(channelsToSubscribe subscription.List) error { return by.handleSpotSubscription("subscribe", channelsToSubscribe) } -func (by *Bybit) handleSubscriptions(assetType asset.Item, operation string, channelsToSubscribe []subscription.Subscription) ([]SubscriptionArgument, error) { +func (by *Bybit) handleSubscriptions(assetType asset.Item, operation string, channelsToSubscribe subscription.List) ([]SubscriptionArgument, error) { var args []SubscriptionArgument arg := SubscriptionArgument{ Operation: operation, @@ -166,17 +166,21 @@ func (by *Bybit) handleSubscriptions(assetType asset.Item, operation string, cha return nil, err } for i := range channelsToSubscribe { + if len(channelsToSubscribe[i].Pairs) != 1 { + return nil, subscription.ErrNotSinglePair + } + pair := channelsToSubscribe[i].Pairs[0] switch channelsToSubscribe[i].Channel { case chanOrderbook: - arg.Arguments = append(arg.Arguments, fmt.Sprintf("%s.%d.%s", channelsToSubscribe[i].Channel, 50, channelsToSubscribe[i].Pair.Format(pairFormat).String())) + arg.Arguments = append(arg.Arguments, fmt.Sprintf("%s.%d.%s", channelsToSubscribe[i].Channel, 50, pair.Format(pairFormat).String())) case chanPublicTrade, chanPublicTicker, chanLiquidation, chanLeverageTokenTicker, chanLeverageTokenNav: - arg.Arguments = append(arg.Arguments, channelsToSubscribe[i].Channel+"."+channelsToSubscribe[i].Pair.Format(pairFormat).String()) + arg.Arguments = append(arg.Arguments, channelsToSubscribe[i].Channel+"."+pair.Format(pairFormat).String()) case chanKline, chanLeverageTokenKline: interval, err := intervalToString(kline.FiveMin) if err != nil { return nil, err } - arg.Arguments = append(arg.Arguments, channelsToSubscribe[i].Channel+"."+interval+"."+channelsToSubscribe[i].Pair.Format(pairFormat).String()) + arg.Arguments = append(arg.Arguments, channelsToSubscribe[i].Channel+"."+interval+"."+pair.Format(pairFormat).String()) case chanPositions, chanExecution, chanOrder, chanWallet, chanGreeks, chanDCP: if chanMap[channelsToSubscribe[i].Channel]&selectedChannels > 0 { continue @@ -204,11 +208,11 @@ func (by *Bybit) handleSubscriptions(assetType asset.Item, operation string, cha } // Unsubscribe sends a websocket message to stop receiving data from the channel -func (by *Bybit) Unsubscribe(channelsToUnsubscribe []subscription.Subscription) error { +func (by *Bybit) Unsubscribe(channelsToUnsubscribe subscription.List) error { return by.handleSpotSubscription("unsubscribe", channelsToUnsubscribe) } -func (by *Bybit) handleSpotSubscription(operation string, channelsToSubscribe []subscription.Subscription) error { +func (by *Bybit) handleSpotSubscription(operation string, channelsToSubscribe subscription.List) error { payloads, err := by.handleSubscriptions(asset.Spot, operation, channelsToSubscribe) if err != nil { return err @@ -239,8 +243,8 @@ func (by *Bybit) handleSpotSubscription(operation string, channelsToSubscribe [] } // GenerateDefaultSubscriptions generates default subscription -func (by *Bybit) GenerateDefaultSubscriptions() ([]subscription.Subscription, error) { - var subscriptions []subscription.Subscription +func (by *Bybit) GenerateDefaultSubscriptions() (subscription.List, error) { + var subscriptions subscription.List var channels = []string{ chanPublicTicker, chanOrderbook, @@ -266,16 +270,16 @@ func (by *Bybit) GenerateDefaultSubscriptions() ([]subscription.Subscription, er chanDCP, chanWallet: subscriptions = append(subscriptions, - subscription.Subscription{ + &subscription.Subscription{ Channel: channels[x], Asset: asset.Spot, }) default: for z := range pairs { subscriptions = append(subscriptions, - subscription.Subscription{ + &subscription.Subscription{ Channel: channels[x], - Pair: pairs[z], + Pairs: currency.Pairs{pairs[z]}, Asset: asset.Spot, }) } diff --git a/exchanges/coinbasepro/coinbasepro_test.go b/exchanges/coinbasepro/coinbasepro_test.go index 7afca094a02..5768c7e54e4 100644 --- a/exchanges/coinbasepro/coinbasepro_test.go +++ b/exchanges/coinbasepro/coinbasepro_test.go @@ -688,20 +688,11 @@ func TestWsAuth(t *testing.T) { } var dialer websocket.Dialer err := c.Websocket.Conn.Dial(&dialer, http.Header{}) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err, "Dial must not error") go c.wsReadData() - err = c.Subscribe([]subscription.Subscription{ - { - Channel: "user", - Pair: testPair, - }, - }) - if err != nil { - t.Error(err) - } + err = c.Subscribe(subscription.List{{Channel: "user", Pairs: currency.Pairs{testPair}}}) + require.NoError(t, err, "Subscribe must not error") timer := time.NewTimer(sharedtestvalues.WebsocketResponseDefaultTimeout) select { case badResponse := <-c.Websocket.DataHandler: diff --git a/exchanges/coinbasepro/coinbasepro_types.go b/exchanges/coinbasepro/coinbasepro_types.go index 2a8765edba7..de2a37284a5 100644 --- a/exchanges/coinbasepro/coinbasepro_types.go +++ b/exchanges/coinbasepro/coinbasepro_types.go @@ -362,17 +362,17 @@ type FillResponse struct { // WebsocketSubscribe takes in subscription information type WebsocketSubscribe struct { - Type string `json:"type"` - ProductIDs []string `json:"product_ids,omitempty"` - Channels []WsChannels `json:"channels,omitempty"` - Signature string `json:"signature,omitempty"` - Key string `json:"key,omitempty"` - Passphrase string `json:"passphrase,omitempty"` - Timestamp string `json:"timestamp,omitempty"` + Type string `json:"type"` + ProductIDs []string `json:"product_ids,omitempty"` + Channels []any `json:"channels,omitempty"` + Signature string `json:"signature,omitempty"` + Key string `json:"key,omitempty"` + Passphrase string `json:"passphrase,omitempty"` + Timestamp string `json:"timestamp,omitempty"` } -// WsChannels defines outgoing channels for subscription purposes -type WsChannels struct { +// WsChannel defines a websocket subscription channel +type WsChannel struct { Name string `json:"name"` ProductIDs []string `json:"product_ids,omitempty"` } diff --git a/exchanges/coinbasepro/coinbasepro_websocket.go b/exchanges/coinbasepro/coinbasepro_websocket.go index 7895d9abb7f..4765eba59b5 100644 --- a/exchanges/coinbasepro/coinbasepro_websocket.go +++ b/exchanges/coinbasepro/coinbasepro_websocket.go @@ -10,11 +10,9 @@ import ( "time" "github.com/gorilla/websocket" - "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/common/convert" "github.com/thrasher-corp/gocryptotrader/common/crypto" "github.com/thrasher-corp/gocryptotrader/currency" - "github.com/thrasher-corp/gocryptotrader/exchanges/account" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" @@ -365,131 +363,105 @@ func (c *CoinbasePro) ProcessUpdate(update *WebsocketL2Update) error { }) } -// GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions() -func (c *CoinbasePro) GenerateDefaultSubscriptions() ([]subscription.Subscription, error) { - var channels = []string{"heartbeat", - "level2_batch", /*Other orderbook feeds require authentication. This is batched in 50ms lots.*/ - "ticker", - "user", - "matches"} - enabledPairs, err := c.GetEnabledPairs(asset.Spot) +// generateSubscriptions returns a list of subscriptions from the configured subscriptions feature +func (c *CoinbasePro) generateSubscriptions() (subscription.List, error) { + pairs, err := c.GetEnabledPairs(asset.Spot) if err != nil { return nil, err } - var subscriptions []subscription.Subscription - for i := range channels { - if (channels[i] == "user" || channels[i] == "full") && - !c.IsWebsocketAuthenticationSupported() { + pairFmt, err := c.GetPairFormat(asset.Spot, true) + if err != nil { + return nil, err + } + pairs = pairs.Format(pairFmt) + authed := c.IsWebsocketAuthenticationSupported() + subs := make(subscription.List, 0, len(c.Features.Subscriptions)) + for _, baseSub := range c.Features.Subscriptions { + if !authed && baseSub.Authenticated { continue } - for j := range enabledPairs { - fPair, err := c.FormatExchangeCurrency(enabledPairs[j], - asset.Spot) - if err != nil { - return nil, err - } - subscriptions = append(subscriptions, subscription.Subscription{ - Channel: channels[i], - Pair: fPair, - Asset: asset.Spot, - }) - } + + s := baseSub.Clone() + s.Asset = asset.Spot + s.Pairs = pairs + subs = append(subs, s) } - return subscriptions, nil + return subs, nil } // Subscribe sends a websocket message to receive data from the channel -func (c *CoinbasePro) Subscribe(channelsToSubscribe []subscription.Subscription) error { - var creds *account.Credentials - var err error - if c.IsWebsocketAuthenticationSupported() { - creds, err = c.GetCredentials(context.TODO()) - if err != nil { - return err - } +func (c *CoinbasePro) Subscribe(subs subscription.List) error { + r := &WebsocketSubscribe{ + Type: "subscribe", + Channels: make([]any, 0, len(subs)), } - - subscribe := WebsocketSubscribe{ - Type: "subscribe", - } - productIDs := make([]string, 0, len(channelsToSubscribe)) - for i := range channelsToSubscribe { - p := channelsToSubscribe[i].Pair.String() - if p != "" && !common.StringDataCompare(productIDs, p) { - // get all unique productIDs in advance as we generate by channels - productIDs = append(productIDs, p) + // See if we have a consistent Pair list for all the subs that we can use globally + // If all the subs have the same pairs then we can use the top level ProductIDs field + // Otherwise each and every sub needs to have it's own list + for i, s := range subs { + if i == 0 { + r.ProductIDs = s.Pairs.Strings() + } else if !subs[0].Pairs.Equal(s.Pairs) { + r.ProductIDs = nil + break } } -subscriptions: - for i := range channelsToSubscribe { - for j := range subscribe.Channels { - if subscribe.Channels[j].Name == channelsToSubscribe[i].Channel { - continue subscriptions - } - } - - subChan := WsChannels{ - Name: channelsToSubscribe[i].Channel, - ProductIDs: productIDs, - } - if (channelsToSubscribe[i].Channel == "user" || channelsToSubscribe[i].Channel == "full") && - creds != nil && - subscribe.Signature == "" { - n := strconv.FormatInt(time.Now().Unix(), 10) - message := n + http.MethodGet + "/users/self/verify" - var hmac []byte - hmac, err = crypto.GetHMAC(crypto.HashSHA256, - []byte(message), - []byte(creds.Secret)) - if err != nil { + for _, s := range subs { + if s.Authenticated && r.Key == "" && c.IsWebsocketAuthenticationSupported() { + if err := c.authWsSubscibeReq(r); err != nil { return err } - subscribe.Signature = crypto.Base64Encode(hmac) - subscribe.Key = creds.Key - subscribe.Passphrase = creds.ClientID - subscribe.Timestamp = n } - subscribe.Channels = append(subscribe.Channels, subChan) + if len(r.ProductIDs) == 0 { + r.Channels = append(r.Channels, WsChannel{ + Name: s.Channel, + ProductIDs: s.Pairs.Strings(), + }) + } else { + // Coinbase does not support using [WsChannel{Name:"x"}] unless each ProductIDs field is populated + // Therefore we have to use Channels as an array of strings + r.Channels = append(r.Channels, s.Channel) + } + } + err := c.Websocket.Conn.SendJSONMessage(r) + if err == nil { + err = c.Websocket.AddSuccessfulSubscriptions(subs...) + } + return err +} + +func (c *CoinbasePro) authWsSubscibeReq(r *WebsocketSubscribe) error { + creds, err := c.GetCredentials(context.TODO()) + if err != nil { + return err } - err = c.Websocket.Conn.SendJSONMessage(subscribe) + r.Timestamp = strconv.FormatInt(time.Now().Unix(), 10) + message := r.Timestamp + http.MethodGet + "/users/self/verify" + hmac, err := crypto.GetHMAC(crypto.HashSHA256, []byte(message), []byte(creds.Secret)) if err != nil { return err } - c.Websocket.AddSuccessfulSubscriptions(channelsToSubscribe...) + r.Signature = crypto.Base64Encode(hmac) + r.Key = creds.Key + r.Passphrase = creds.ClientID return nil } // Unsubscribe sends a websocket message to stop receiving data from the channel -func (c *CoinbasePro) Unsubscribe(channelsToUnsubscribe []subscription.Subscription) error { - unsubscribe := WebsocketSubscribe{ - Type: "unsubscribe", +func (c *CoinbasePro) Unsubscribe(subs subscription.List) error { + r := &WebsocketSubscribe{ + Type: "unsubscribe", + Channels: make([]any, 0, len(subs)), } - productIDs := make([]string, 0, len(channelsToUnsubscribe)) - for i := range channelsToUnsubscribe { - p := channelsToUnsubscribe[i].Pair.String() - if p != "" && !common.StringDataCompare(productIDs, p) { - // get all unique productIDs in advance as we generate by channels - productIDs = append(productIDs, p) - } - } - -unsubscriptions: - for i := range channelsToUnsubscribe { - for j := range unsubscribe.Channels { - if unsubscribe.Channels[j].Name == channelsToUnsubscribe[i].Channel { - continue unsubscriptions - } - } - - unsubscribe.Channels = append(unsubscribe.Channels, WsChannels{ - Name: channelsToUnsubscribe[i].Channel, - ProductIDs: productIDs, + for _, s := range subs { + r.Channels = append(r.Channels, WsChannel{ + Name: s.Channel, + ProductIDs: s.Pairs.Strings(), }) } - err := c.Websocket.Conn.SendJSONMessage(unsubscribe) - if err != nil { - return err + err := c.Websocket.Conn.SendJSONMessage(r) + if err == nil { + err = c.Websocket.RemoveSubscriptions(subs...) } - c.Websocket.RemoveSubscriptions(channelsToUnsubscribe...) - return nil + return err } diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index 14d837280aa..1e548b3dc23 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -23,6 +23,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/request" "github.com/thrasher-corp/gocryptotrader/exchanges/stream" "github.com/thrasher-corp/gocryptotrader/exchanges/stream/buffer" + "github.com/thrasher-corp/gocryptotrader/exchanges/subscription" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" "github.com/thrasher-corp/gocryptotrader/exchanges/trade" "github.com/thrasher-corp/gocryptotrader/log" @@ -105,6 +106,13 @@ func (c *CoinbasePro) SetDefaults() { GlobalResultLimit: 300, }, }, + Subscriptions: subscription.List{ + {Enabled: true, Channel: "heartbeat"}, + {Enabled: true, Channel: "level2_batch"}, // Other orderbook feeds require authentication; This is batched in 50ms lots + {Enabled: true, Channel: "ticker"}, + {Enabled: true, Channel: "user", Authenticated: true}, + {Enabled: true, Channel: "matches"}, + }, } c.Requester, err = request.New(c.Name, @@ -155,7 +163,7 @@ func (c *CoinbasePro) Setup(exch *config.Exchange) error { Connector: c.WsConnect, Subscriber: c.Subscribe, Unsubscriber: c.Unsubscribe, - GenerateSubscriptions: c.GenerateDefaultSubscriptions, + GenerateSubscriptions: c.generateSubscriptions, Features: &c.Features.Supports.WebsocketCapabilities, OrderbookBufferConfig: buffer.Config{ SortBuffer: true, diff --git a/exchanges/coinut/coinut_websocket.go b/exchanges/coinut/coinut_websocket.go index b351cbba6d9..b87792aede2 100644 --- a/exchanges/coinut/coinut_websocket.go +++ b/exchanges/coinut/coinut_websocket.go @@ -580,18 +580,18 @@ func (c *COINUT) WsProcessOrderbookUpdate(update *WsOrderbookUpdate) error { } // GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions() -func (c *COINUT) GenerateDefaultSubscriptions() ([]subscription.Subscription, error) { +func (c *COINUT) GenerateDefaultSubscriptions() (subscription.List, error) { var channels = []string{"inst_tick", "inst_order_book", "inst_trade"} - var subscriptions []subscription.Subscription + var subscriptions subscription.List enabledPairs, err := c.GetEnabledPairs(asset.Spot) if err != nil { return nil, err } for i := range channels { for j := range enabledPairs { - subscriptions = append(subscriptions, subscription.Subscription{ + subscriptions = append(subscriptions, &subscription.Subscription{ Channel: channels[i], - Pair: enabledPairs[j], + Pairs: currency.Pairs{enabledPairs[j]}, Asset: asset.Spot, }) } @@ -600,46 +600,50 @@ func (c *COINUT) GenerateDefaultSubscriptions() ([]subscription.Subscription, er } // Subscribe sends a websocket message to receive data from the channel -func (c *COINUT) Subscribe(channelsToSubscribe []subscription.Subscription) error { +func (c *COINUT) Subscribe(subs subscription.List) error { var errs error - for i := range channelsToSubscribe { - fPair, err := c.FormatExchangeCurrency(channelsToSubscribe[i].Pair, asset.Spot) + for _, s := range subs { + if len(s.Pairs) != 1 { + return subscription.ErrNotSinglePair + } + fPair, err := c.FormatExchangeCurrency(s.Pairs[0], asset.Spot) if err != nil { errs = common.AppendError(errs, err) continue } subscribe := wsRequest{ - Request: channelsToSubscribe[i].Channel, + Request: s.Channel, InstrumentID: c.instrumentMap.LookupID(fPair.String()), Subscribe: true, Nonce: getNonce(), } err = c.Websocket.Conn.SendJSONMessage(subscribe) + if err == nil { + err = c.Websocket.AddSuccessfulSubscriptions(s) + } if err != nil { errs = common.AppendError(errs, err) - continue } - c.Websocket.AddSuccessfulSubscriptions(channelsToSubscribe[i]) - } - if errs != nil { - return errs } - return nil + return errs } // Unsubscribe sends a websocket message to stop receiving data from the channel -func (c *COINUT) Unsubscribe(channelToUnsubscribe []subscription.Subscription) error { +func (c *COINUT) Unsubscribe(channelToUnsubscribe subscription.List) error { var errs error - for i := range channelToUnsubscribe { - fPair, err := c.FormatExchangeCurrency(channelToUnsubscribe[i].Pair, asset.Spot) + for _, s := range channelToUnsubscribe { + if len(s.Pairs) != 1 { + return subscription.ErrNotSinglePair + } + fPair, err := c.FormatExchangeCurrency(s.Pairs[0], asset.Spot) if err != nil { errs = common.AppendError(errs, err) continue } subscribe := wsRequest{ - Request: channelToUnsubscribe[i].Channel, + Request: s.Channel, InstrumentID: c.instrumentMap.LookupID(fPair.String()), Subscribe: false, Nonce: getNonce(), @@ -651,22 +655,20 @@ func (c *COINUT) Unsubscribe(channelToUnsubscribe []subscription.Subscription) e } var response map[string]interface{} err = json.Unmarshal(resp, &response) + if err == nil { + val, ok := response["status"].([]any) + switch { + case !ok: + err = common.GetTypeAssertError("[]any", response["status"]) + case len(val) == 0, val[0] != "OK": + err = common.AppendError(errs, fmt.Errorf("%v unsubscribe failed for channel %v", c.Name, s.Channel)) + default: + err = c.Websocket.RemoveSubscriptions(s) + } + } if err != nil { errs = common.AppendError(errs, err) - continue - } - - val, ok := response["status"].([]interface{}) - if !ok { - errs = common.AppendError(errs, errors.New("unable to type assert response status")) - } - if val[0] != "OK" { - errs = common.AppendError(errs, fmt.Errorf("%v unsubscribe failed for channel %v", - c.Name, - channelToUnsubscribe[i].Channel)) - continue } - c.Websocket.RemoveSubscriptions(channelToUnsubscribe[i]) } return errs } diff --git a/exchanges/deribit/deribit_websocket.go b/exchanges/deribit/deribit_websocket.go index fae7bf50cb8..44647643e1b 100644 --- a/exchanges/deribit/deribit_websocket.go +++ b/exchanges/deribit/deribit_websocket.go @@ -834,8 +834,8 @@ func (d *Deribit) processOrderbook(respRaw []byte, channels []string) error { } // GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions() -func (d *Deribit) GenerateDefaultSubscriptions() ([]subscription.Subscription, error) { - var subscriptions []subscription.Subscription +func (d *Deribit) GenerateDefaultSubscriptions() (subscription.List, error) { + var subscriptions subscription.List assets := d.GetAssetTypes(true) subscriptionChannels := defaultSubscriptions if d.Websocket.CanUseAuthenticatedEndpoints() { @@ -870,9 +870,9 @@ func (d *Deribit) GenerateDefaultSubscriptions() ([]subscription.Subscription, e continue } subscriptions = append(subscriptions, - subscription.Subscription{ + &subscription.Subscription{ Channel: subscriptionChannels[x], - Pair: assetPairs[a][z], + Pairs: currency.Pairs{assetPairs[a][z]}, Params: map[string]interface{}{ "resolution": "1D", }, @@ -890,9 +890,9 @@ func (d *Deribit) GenerateDefaultSubscriptions() ([]subscription.Subscription, e a == asset.Futures) || (a != asset.Spot && a != asset.Futures) { continue } - subscriptions = append(subscriptions, subscription.Subscription{ + subscriptions = append(subscriptions, &subscription.Subscription{ Channel: subscriptionChannels[x], - Pair: assetPairs[a][z], + Pairs: currency.Pairs{assetPairs[a][z]}, Asset: a, }) } @@ -906,9 +906,9 @@ func (d *Deribit) GenerateDefaultSubscriptions() ([]subscription.Subscription, e continue } subscriptions = append(subscriptions, - subscription.Subscription{ + &subscription.Subscription{ Channel: subscriptionChannels[x], - Pair: assetPairs[a][z], + Pairs: currency.Pairs{assetPairs[a][z]}, // if needed, group and depth of orderbook can be passed as follow "group": "250", "depth": "20", Interval: kline.HundredMilliseconds, Asset: a, @@ -919,9 +919,9 @@ func (d *Deribit) GenerateDefaultSubscriptions() ([]subscription.Subscription, e }, ) if d.Websocket.CanUseAuthenticatedEndpoints() { - subscriptions = append(subscriptions, subscription.Subscription{ + subscriptions = append(subscriptions, &subscription.Subscription{ Channel: orderbookChannel, - Pair: assetPairs[a][z], + Pairs: currency.Pairs{assetPairs[a][z]}, Asset: a, Interval: kline.Interval(0), Params: map[string]interface{}{ @@ -942,9 +942,9 @@ func (d *Deribit) GenerateDefaultSubscriptions() ([]subscription.Subscription, e continue } subscriptions = append(subscriptions, - subscription.Subscription{ + &subscription.Subscription{ Channel: subscriptionChannels[x], - Pair: assetPairs[a][z], + Pairs: currency.Pairs{assetPairs[a][z]}, Interval: kline.HundredMilliseconds, Asset: a, }) @@ -959,9 +959,9 @@ func (d *Deribit) GenerateDefaultSubscriptions() ([]subscription.Subscription, e continue } subscriptions = append(subscriptions, - subscription.Subscription{ + &subscription.Subscription{ Channel: subscriptionChannels[x], - Pair: assetPairs[a][z], + Pairs: currency.Pairs{assetPairs[a][z]}, Interval: kline.HundredMilliseconds, Asset: a, }) @@ -974,18 +974,18 @@ func (d *Deribit) GenerateDefaultSubscriptions() ([]subscription.Subscription, e currencyPairsName := make(map[currency.Code]bool, 2*len(assetPairs[a])) for z := range assetPairs[a] { if okay = currencyPairsName[assetPairs[a][z].Base]; !okay { - subscriptions = append(subscriptions, subscription.Subscription{ + subscriptions = append(subscriptions, &subscription.Subscription{ Asset: a, Channel: subscriptionChannels[x], - Pair: currency.Pair{Base: assetPairs[a][z].Base}, + Pairs: currency.Pairs{currency.Pair{Base: assetPairs[a][z].Base}}, }) currencyPairsName[assetPairs[a][z].Base] = true } if okay = currencyPairsName[assetPairs[a][z].Quote]; !okay { - subscriptions = append(subscriptions, subscription.Subscription{ + subscriptions = append(subscriptions, &subscription.Subscription{ Asset: a, Channel: subscriptionChannels[x], - Pair: currency.Pair{Base: assetPairs[a][z].Quote}, + Pairs: currency.Pairs{currency.Pair{Base: assetPairs[a][z].Quote}}, }) currencyPairsName[assetPairs[a][z].Quote] = true } @@ -1001,19 +1001,19 @@ func (d *Deribit) GenerateDefaultSubscriptions() ([]subscription.Subscription, e var okay bool for z := range assetPairs[a] { if okay = currencyPairsName[assetPairs[a][z].Base]; !okay { - subscriptions = append(subscriptions, subscription.Subscription{ + subscriptions = append(subscriptions, &subscription.Subscription{ Asset: a, Channel: subscriptionChannels[x], - Pair: currency.Pair{Base: assetPairs[a][z].Base}, + Pairs: currency.Pairs{currency.Pair{Base: assetPairs[a][z].Base}}, Interval: kline.HundredMilliseconds, }) currencyPairsName[assetPairs[a][z].Base] = true } if okay = currencyPairsName[assetPairs[a][z].Quote]; !okay { - subscriptions = append(subscriptions, subscription.Subscription{ + subscriptions = append(subscriptions, &subscription.Subscription{ Asset: a, Channel: subscriptionChannels[x], - Pair: currency.Pair{Base: assetPairs[a][z].Quote}, + Pairs: currency.Pairs{currency.Pair{Base: assetPairs[a][z].Quote}}, Interval: kline.HundredMilliseconds, }) currencyPairsName[assetPairs[a][z].Quote] = true @@ -1028,17 +1028,17 @@ func (d *Deribit) GenerateDefaultSubscriptions() ([]subscription.Subscription, e var okay bool for z := range assetPairs[a] { if okay = currencyPairsName[assetPairs[a][z].Base]; !okay { - subscriptions = append(subscriptions, subscription.Subscription{ + subscriptions = append(subscriptions, &subscription.Subscription{ Channel: subscriptionChannels[x], - Pair: currency.Pair{Base: assetPairs[a][z].Base}, + Pairs: currency.Pairs{currency.Pair{Base: assetPairs[a][z].Base}}, Asset: a, }) currencyPairsName[assetPairs[a][z].Base] = true } if okay = currencyPairsName[assetPairs[a][z].Quote]; !okay { - subscriptions = append(subscriptions, subscription.Subscription{ + subscriptions = append(subscriptions, &subscription.Subscription{ Channel: subscriptionChannels[x], - Pair: currency.Pair{Base: assetPairs[a][z].Quote}, + Pairs: currency.Pairs{currency.Pair{Base: assetPairs[a][z].Quote}}, Asset: a, }) currencyPairsName[assetPairs[a][z].Quote] = true @@ -1050,7 +1050,7 @@ func (d *Deribit) GenerateDefaultSubscriptions() ([]subscription.Subscription, e platformStateChannel, userLockChannel, platformStatePublicMethodsStateChannel: - subscriptions = append(subscriptions, subscription.Subscription{ + subscriptions = append(subscriptions, &subscription.Subscription{ Channel: subscriptionChannels[x], }) case priceIndexChannel, @@ -1060,7 +1060,7 @@ func (d *Deribit) GenerateDefaultSubscriptions() ([]subscription.Subscription, e markPriceOptionsChannel, estimatedExpirationPriceChannel: for i := range indexENUMS { - subscriptions = append(subscriptions, subscription.Subscription{ + subscriptions = append(subscriptions, &subscription.Subscription{ Channel: subscriptionChannels[x], Params: map[string]interface{}{ "index_name": indexENUMS[i], @@ -1072,9 +1072,12 @@ func (d *Deribit) GenerateDefaultSubscriptions() ([]subscription.Subscription, e return subscriptions, nil } -func (d *Deribit) generatePayloadFromSubscriptionInfos(operation string, subscs []subscription.Subscription) ([]WsSubscriptionInput, error) { +func (d *Deribit) generatePayloadFromSubscriptionInfos(operation string, subscs subscription.List) ([]WsSubscriptionInput, error) { subscriptionPayloads := make([]WsSubscriptionInput, len(subscs)) for x := range subscs { + if len(subscs[x].Pairs) > 1 { + return nil, subscription.ErrNotSinglePair + } sub := WsSubscriptionInput{ JSONRPCVersion: rpcVersion, ID: d.Websocket.Conn.GenerateMessageID(false), @@ -1090,16 +1093,16 @@ func (d *Deribit) generatePayloadFromSubscriptionInfos(operation string, subscs sub.Method = "private/" + operation } var instrumentID string - if !subscs[x].Pair.IsEmpty() { + if len(subscs[x].Pairs) == 1 { pairFormat, err := d.GetPairFormat(subscs[x].Asset, true) if err != nil { return nil, err } - subscs[x].Pair = subscs[x].Pair.Format(pairFormat) + subscs[x].Pairs = subscs[x].Pairs.Format(pairFormat) if subscs[x].Asset == asset.Futures { - instrumentID = d.formatFuturesTradablePair(subscs[x].Pair.Format(pairFormat)) + instrumentID = d.formatFuturesTradablePair(subscs[x].Pairs[0]) } else { - instrumentID = subscs[x].Pair.String() + instrumentID = subscs[x].Pairs.Join() } } switch subscs[x].Channel { @@ -1110,7 +1113,7 @@ func (d *Deribit) generatePayloadFromSubscriptionInfos(operation string, subscs userLockChannel: sub.Params["channels"] = []string{subscs[x].Channel} case orderbookChannel: - if subscs[x].Pair.IsEmpty() { + if len(subscs[x].Pairs) != 1 { return nil, currency.ErrCurrencyPairEmpty } intervalString, err := d.GetResolutionFromInterval(subscs[x].Interval) @@ -1129,14 +1132,14 @@ func (d *Deribit) generatePayloadFromSubscriptionInfos(operation string, subscs } sub.Params["channels"] = []string{orderbookChannel + "." + instrumentID + "." + group + "." + depth + "." + intervalString} case chartTradesChannel: - if subscs[x].Pair.IsEmpty() { + if len(subscs[x].Pairs) != 1 { return nil, currency.ErrCurrencyPairEmpty } resolution, okay := subscs[x].Params["resolution"].(string) if !okay { resolution = "1D" } - sub.Params["channels"] = []string{chartTradesChannel + "." + d.formatFuturesTradablePair(subscs[x].Pair) + "." + resolution} + sub.Params["channels"] = []string{chartTradesChannel + "." + d.formatFuturesTradablePair(subscs[x].Pairs[0]) + "." + resolution} case priceIndexChannel, priceRankingChannel, priceStatisticsChannel, @@ -1149,28 +1152,37 @@ func (d *Deribit) generatePayloadFromSubscriptionInfos(operation string, subscs } sub.Params["channels"] = []string{subscs[x].Channel + "." + indexName} case instrumentStateChannel: + if len(subscs[x].Pairs) != 1 { + return nil, currency.ErrCurrencyPairEmpty + } kind := d.GetAssetKind(subscs[x].Asset) - currencyCode := getValidatedCurrencyCode(subscs[x].Pair) + currencyCode := getValidatedCurrencyCode(subscs[x].Pairs[0]) sub.Params["channels"] = []string{"instrument.state." + kind + "." + currencyCode} case rawUsersOrdersKindCurrencyChannel: + if len(subscs[x].Pairs) != 1 { + return nil, currency.ErrCurrencyPairEmpty + } kind := d.GetAssetKind(subscs[x].Asset) - currencyCode := getValidatedCurrencyCode(subscs[x].Pair) + currencyCode := getValidatedCurrencyCode(subscs[x].Pairs[0]) sub.Params["channels"] = []string{"user.orders." + kind + "." + currencyCode + ".raw"} case quoteChannel, incrementalTickerChannel: - if subscs[x].Pair.IsEmpty() { + if len(subscs[x].Pairs) != 1 { return nil, currency.ErrCurrencyPairEmpty } sub.Params["channels"] = []string{subscs[x].Channel + "." + instrumentID} case rawUserOrdersChannel: - if subscs[x].Pair.IsEmpty() { + if len(subscs[x].Pairs) != 1 { return nil, currency.ErrCurrencyPairEmpty } sub.Params["channels"] = []string{"user.orders." + instrumentID + ".raw"} case requestForQuoteChannel, userMMPTriggerChannel, userPortfolioChannel: - currencyCode := getValidatedCurrencyCode(subscs[x].Pair) + if len(subscs[x].Pairs) != 1 { + return nil, currency.ErrCurrencyPairEmpty + } + currencyCode := getValidatedCurrencyCode(subscs[x].Pairs[0]) sub.Params["channels"] = []string{subscs[x].Channel + "." + currencyCode} case tradesChannel, userChangesInstrumentsChannel, @@ -1178,7 +1190,7 @@ func (d *Deribit) generatePayloadFromSubscriptionInfos(operation string, subscs tickerChannel, perpetualChannel, userTradesChannelByInstrument: - if subscs[x].Pair.IsEmpty() { + if len(subscs[x].Pairs) != 1 { return nil, currency.ErrCurrencyPairEmpty } if subscs[x].Interval.Duration() == 0 { @@ -1195,7 +1207,10 @@ func (d *Deribit) generatePayloadFromSubscriptionInfos(operation string, subscs rawUsersOrdersWithKindCurrencyAndIntervalChannel, userTradesByKindCurrencyAndIntervalChannel: kind := d.GetAssetKind(subscs[x].Asset) - currencyCode := getValidatedCurrencyCode(subscs[x].Pair) + if len(subscs[x].Pairs) != 1 { + return nil, currency.ErrCurrencyPairEmpty + } + currencyCode := getValidatedCurrencyCode(subscs[x].Pairs[0]) if subscs[x].Interval.Duration() == 0 { sub.Params["channels"] = []string{subscs[x].Channel + "." + kind + "." + currencyCode} continue @@ -1214,12 +1229,12 @@ func (d *Deribit) generatePayloadFromSubscriptionInfos(operation string, subscs } // Subscribe sends a websocket message to receive data from the channel -func (d *Deribit) Subscribe(channelsToSubscribe []subscription.Subscription) error { +func (d *Deribit) Subscribe(channelsToSubscribe subscription.List) error { return d.handleSubscription("subscribe", channelsToSubscribe) } // Unsubscribe sends a websocket message to stop receiving data from the channel -func (d *Deribit) Unsubscribe(channelsToUnsubscribe []subscription.Subscription) error { +func (d *Deribit) Unsubscribe(channelsToUnsubscribe subscription.List) error { return d.handleSubscription("unsubscribe", channelsToUnsubscribe) } @@ -1238,7 +1253,7 @@ func filterSubscriptionPayloads(subscription []WsSubscriptionInput) []WsSubscrip return newSubscs } -func (d *Deribit) handleSubscription(operation string, channels []subscription.Subscription) error { +func (d *Deribit) handleSubscription(operation string, channels subscription.List) error { payloads, err := d.generatePayloadFromSubscriptionInfos(operation, channels) if err != nil { return err diff --git a/exchanges/exchange.go b/exchanges/exchange.go index 5644d6afecf..bb67b9844a5 100644 --- a/exchanges/exchange.go +++ b/exchanges/exchange.go @@ -168,10 +168,10 @@ func (b *Base) SetSubscriptionsFromConfig() { b.settingsMutex.Lock() defer b.settingsMutex.Unlock() if len(b.Config.Features.Subscriptions) == 0 { + // Set config from the defaults, including any disabled subscriptions b.Config.Features.Subscriptions = b.Features.Subscriptions - return } - b.Features.Subscriptions = []*subscription.Subscription{} + b.Features.Subscriptions = subscription.List{} for _, s := range b.Config.Features.Subscriptions { if s.Enabled { b.Features.Subscriptions = append(b.Features.Subscriptions, s) @@ -1126,7 +1126,7 @@ func (b *Base) FlushWebsocketChannels() error { // SubscribeToWebsocketChannels appends to ChannelsToSubscribe // which lets websocket.manageSubscriptions handle subscribing -func (b *Base) SubscribeToWebsocketChannels(channels []subscription.Subscription) error { +func (b *Base) SubscribeToWebsocketChannels(channels subscription.List) error { if b.Websocket == nil { return common.ErrFunctionNotSupported } @@ -1135,7 +1135,7 @@ func (b *Base) SubscribeToWebsocketChannels(channels []subscription.Subscription // UnsubscribeToWebsocketChannels removes from ChannelsToSubscribe // which lets websocket.manageSubscriptions handle unsubscribing -func (b *Base) UnsubscribeToWebsocketChannels(channels []subscription.Subscription) error { +func (b *Base) UnsubscribeToWebsocketChannels(channels subscription.List) error { if b.Websocket == nil { return common.ErrFunctionNotSupported } @@ -1143,7 +1143,7 @@ func (b *Base) UnsubscribeToWebsocketChannels(channels []subscription.Subscripti } // GetSubscriptions returns a copied list of subscriptions -func (b *Base) GetSubscriptions() ([]subscription.Subscription, error) { +func (b *Base) GetSubscriptions() (subscription.List, error) { if b.Websocket == nil { return nil, common.ErrFunctionNotSupported } @@ -1804,7 +1804,7 @@ func (b *Base) GetOpenInterest(context.Context, ...key.PairAsset) ([]futures.Ope } // ParallelChanOp performs a single method call in parallel across streams and waits to return any errors -func (b *Base) ParallelChanOp(channels []subscription.Subscription, m func([]subscription.Subscription) error, batchSize int) error { +func (b *Base) ParallelChanOp(channels subscription.List, m func(subscription.List) error, batchSize int) error { wg := sync.WaitGroup{} errC := make(chan error, len(channels)) if batchSize == 0 { @@ -1818,7 +1818,7 @@ func (b *Base) ParallelChanOp(channels []subscription.Subscription, m func([]sub j = len(channels) } wg.Add(1) - go func(c []subscription.Subscription) { + go func(c subscription.List) { defer wg.Done() if err := m(c); err != nil { errC <- err diff --git a/exchanges/exchange_test.go b/exchanges/exchange_test.go index 5917eb9d04d..76d2d4a5d9d 100644 --- a/exchanges/exchange_test.go +++ b/exchanges/exchange_test.go @@ -880,8 +880,8 @@ func TestSetupDefaults(t *testing.T) { DefaultURL: "ws://something.com", RunningURL: "ws://something.com", Connector: func() error { return nil }, - GenerateSubscriptions: func() ([]subscription.Subscription, error) { return []subscription.Subscription{}, nil }, - Subscriber: func([]subscription.Subscription) error { return nil }, + GenerateSubscriptions: func() (subscription.List, error) { return subscription.List{}, nil }, + Subscriber: func(subscription.List) error { return nil }, }) if err != nil { t.Fatal(err) @@ -1207,8 +1207,8 @@ func TestIsWebsocketEnabled(t *testing.T) { DefaultURL: "ws://something.com", RunningURL: "ws://something.com", Connector: func() error { return nil }, - GenerateSubscriptions: func() ([]subscription.Subscription, error) { return nil, nil }, - Subscriber: func([]subscription.Subscription) error { return nil }, + GenerateSubscriptions: func() (subscription.List, error) { return nil, nil }, + Subscriber: func(subscription.List) error { return nil }, }) if err != nil { t.Error(err) @@ -1645,15 +1645,11 @@ func TestSubscribeToWebsocketChannels(t *testing.T) { func TestUnsubscribeToWebsocketChannels(t *testing.T) { b := Base{} err := b.UnsubscribeToWebsocketChannels(nil) - if err == nil { - t.Fatal(err) - } + assert.ErrorIs(t, err, common.ErrFunctionNotSupported, "UnsubscribeToWebsocketChannels should error correctly with a nil Websocket") b.Websocket = &stream.Websocket{} err = b.UnsubscribeToWebsocketChannels(nil) - if err == nil { - t.Fatal(err) - } + assert.NoError(t, err, "UnsubscribeToWebsocketChannels from an empty/nil list should not error") } func TestGetSubscriptions(t *testing.T) { @@ -2827,27 +2823,29 @@ func TestSetSubscriptionsFromConfig(t *testing.T) { Features: &config.FeaturesConfig{}, }, } - subs := []*subscription.Subscription{ + subs := subscription.List{ {Channel: subscription.CandlesChannel, Interval: kline.OneDay, Enabled: true}, + {Channel: subscription.OrderbookChannel, Enabled: false}, } b.Features.Subscriptions = subs b.SetSubscriptionsFromConfig() assert.ElementsMatch(t, subs, b.Config.Features.Subscriptions, "Config Subscriptions should be updated") - assert.ElementsMatch(t, subs, b.Features.Subscriptions, "Subscriptions should be the same") + assert.ElementsMatch(t, subscription.List{subs[0]}, b.Features.Subscriptions, "Actual Subscriptions should only contain Enabled") - subs = []*subscription.Subscription{ - {Channel: subscription.OrderbookChannel, Interval: kline.OneDay, Enabled: true}, + subs = subscription.List{ + {Channel: subscription.OrderbookChannel, Enabled: true}, + {Channel: subscription.CandlesChannel, Interval: kline.OneDay, Enabled: false}, } b.Config.Features.Subscriptions = subs b.SetSubscriptionsFromConfig() - assert.ElementsMatch(t, subs, b.Features.Subscriptions, "Subscriptions should be updated from Config") assert.ElementsMatch(t, subs, b.Config.Features.Subscriptions, "Config Subscriptions should be the same") + assert.ElementsMatch(t, subscription.List{subs[0]}, b.Features.Subscriptions, "Subscriptions should only contain Enabled from Config") } // TestParallelChanOp unit tests the helper func ParallelChanOp func TestParallelChanOp(t *testing.T) { t.Parallel() - c := []subscription.Subscription{ + c := subscription.List{ {Channel: "red"}, {Channel: "blue"}, {Channel: "violent"}, @@ -2858,7 +2856,7 @@ func TestParallelChanOp(t *testing.T) { b := Base{} errC := make(chan error, 1) go func() { - errC <- b.ParallelChanOp(c, func(c []subscription.Subscription) error { + errC <- b.ParallelChanOp(c, func(c subscription.List) error { time.Sleep(300 * time.Millisecond) run <- struct{}{} switch c[0].Channel { diff --git a/exchanges/exchange_types.go b/exchanges/exchange_types.go index be50855c91e..c94b29f641b 100644 --- a/exchanges/exchange_types.go +++ b/exchanges/exchange_types.go @@ -152,7 +152,7 @@ type WithdrawalHistory struct { type Features struct { Supports FeaturesSupported Enabled FeaturesEnabled - Subscriptions []*subscription.Subscription + Subscriptions subscription.List } // FeaturesEnabled stores the exchange enabled features diff --git a/exchanges/gateio/gateio_websocket.go b/exchanges/gateio/gateio_websocket.go index 67e941dc17d..5ed81aaf643 100644 --- a/exchanges/gateio/gateio_websocket.go +++ b/exchanges/gateio/gateio_websocket.go @@ -625,7 +625,7 @@ func (g *Gateio) processCrossMarginLoans(data []byte) error { } // GenerateDefaultSubscriptions returns default subscriptions -func (g *Gateio) GenerateDefaultSubscriptions() ([]subscription.Subscription, error) { +func (g *Gateio) GenerateDefaultSubscriptions() (subscription.List, error) { channelsToSubscribe := defaultSubscriptions if g.Websocket.CanUseAuthenticatedEndpoints() { channelsToSubscribe = append(channelsToSubscribe, []string{ @@ -638,7 +638,7 @@ func (g *Gateio) GenerateDefaultSubscriptions() ([]subscription.Subscription, er channelsToSubscribe = append(channelsToSubscribe, spotTradesChannel) } - var subscriptions []subscription.Subscription + var subscriptions subscription.List var err error for i := range channelsToSubscribe { var pairs []currency.Pair @@ -678,9 +678,9 @@ func (g *Gateio) GenerateDefaultSubscriptions() ([]subscription.Subscription, er return nil, err } - subscriptions = append(subscriptions, subscription.Subscription{ + subscriptions = append(subscriptions, &subscription.Subscription{ Channel: channelsToSubscribe[i], - Pair: fpair.Upper(), + Pairs: currency.Pairs{fpair.Upper()}, Asset: assetType, Params: params, }) @@ -690,7 +690,7 @@ func (g *Gateio) GenerateDefaultSubscriptions() ([]subscription.Subscription, er } // handleSubscription sends a websocket message to receive data from the channel -func (g *Gateio) handleSubscription(event string, channelsToSubscribe []subscription.Subscription) error { +func (g *Gateio) handleSubscription(event string, channelsToSubscribe subscription.List) error { payloads, err := g.generatePayload(event, channelsToSubscribe) if err != nil { return err @@ -711,16 +711,19 @@ func (g *Gateio) handleSubscription(event string, channelsToSubscribe []subscrip continue } if payloads[k].Event == "subscribe" { - g.Websocket.AddSuccessfulSubscriptions(channelsToSubscribe[k]) + err = g.Websocket.AddSuccessfulSubscriptions(channelsToSubscribe[k]) } else { - g.Websocket.RemoveSubscriptions(channelsToSubscribe[k]) + err = g.Websocket.RemoveSubscriptions(channelsToSubscribe[k]) + } + if err != nil { + errs = common.AppendError(errs, err) } } } return errs } -func (g *Gateio) generatePayload(event string, channelsToSubscribe []subscription.Subscription) ([]WsInput, error) { +func (g *Gateio) generatePayload(event string, channelsToSubscribe subscription.List) ([]WsInput, error) { if len(channelsToSubscribe) == 0 { return nil, errors.New("cannot generate payload, no channels supplied") } @@ -736,10 +739,13 @@ func (g *Gateio) generatePayload(event string, channelsToSubscribe []subscriptio var intervalString string payloads := make([]WsInput, 0, len(channelsToSubscribe)) for i := range channelsToSubscribe { + if len(channelsToSubscribe[i].Pairs) != 1 { + return nil, subscription.ErrNotSinglePair + } var auth *WsAuthInput timestamp := time.Now() - channelsToSubscribe[i].Pair.Delimiter = currency.UnderscoreDelimiter - params := []string{channelsToSubscribe[i].Pair.String()} + channelsToSubscribe[i].Pairs[0].Delimiter = currency.UnderscoreDelimiter + params := []string{channelsToSubscribe[i].Pairs[0].String()} switch channelsToSubscribe[i].Channel { case spotOrderbookChannel: interval, okay := channelsToSubscribe[i].Params["interval"].(kline.Interval) @@ -837,12 +843,12 @@ func (g *Gateio) generatePayload(event string, channelsToSubscribe []subscriptio } // Subscribe sends a websocket message to stop receiving data from the channel -func (g *Gateio) Subscribe(channelsToUnsubscribe []subscription.Subscription) error { +func (g *Gateio) Subscribe(channelsToUnsubscribe subscription.List) error { return g.handleSubscription("subscribe", channelsToUnsubscribe) } // Unsubscribe sends a websocket message to stop receiving data from the channel -func (g *Gateio) Unsubscribe(channelsToUnsubscribe []subscription.Subscription) error { +func (g *Gateio) Unsubscribe(channelsToUnsubscribe subscription.List) error { return g.handleSubscription("unsubscribe", channelsToUnsubscribe) } diff --git a/exchanges/gateio/gateio_ws_delivery_futures.go b/exchanges/gateio/gateio_ws_delivery_futures.go index 449181c1007..b9242981033 100644 --- a/exchanges/gateio/gateio_ws_delivery_futures.go +++ b/exchanges/gateio/gateio_ws_delivery_futures.go @@ -12,6 +12,7 @@ import ( "github.com/gorilla/websocket" "github.com/thrasher-corp/gocryptotrader/common" + "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/exchanges/account" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/kline" @@ -141,7 +142,7 @@ func (g *Gateio) wsFunnelDeliveryFuturesConnectionData(ws stream.Connection) { } // GenerateDeliveryFuturesDefaultSubscriptions returns delivery futures default subscriptions params. -func (g *Gateio) GenerateDeliveryFuturesDefaultSubscriptions() ([]subscription.Subscription, error) { +func (g *Gateio) GenerateDeliveryFuturesDefaultSubscriptions() (subscription.List, error) { _, err := g.GetCredentials(context.Background()) if err != nil { g.Websocket.SetCanUseAuthenticatedEndpoints(false) @@ -159,7 +160,7 @@ func (g *Gateio) GenerateDeliveryFuturesDefaultSubscriptions() ([]subscription.S if err != nil { return nil, err } - var subscriptions []subscription.Subscription + var subscriptions subscription.List for i := range channelsToSubscribe { for j := range pairs { params := make(map[string]interface{}) @@ -174,9 +175,9 @@ func (g *Gateio) GenerateDeliveryFuturesDefaultSubscriptions() ([]subscription.S if err != nil { return nil, err } - subscriptions = append(subscriptions, subscription.Subscription{ + subscriptions = append(subscriptions, &subscription.Subscription{ Channel: channelsToSubscribe[i], - Pair: fpair.Upper(), + Pairs: currency.Pairs{fpair.Upper()}, Params: params, }) } @@ -185,17 +186,17 @@ func (g *Gateio) GenerateDeliveryFuturesDefaultSubscriptions() ([]subscription.S } // DeliveryFuturesSubscribe sends a websocket message to stop receiving data from the channel -func (g *Gateio) DeliveryFuturesSubscribe(channelsToUnsubscribe []subscription.Subscription) error { +func (g *Gateio) DeliveryFuturesSubscribe(channelsToUnsubscribe subscription.List) error { return g.handleDeliveryFuturesSubscription("subscribe", channelsToUnsubscribe) } // DeliveryFuturesUnsubscribe sends a websocket message to stop receiving data from the channel -func (g *Gateio) DeliveryFuturesUnsubscribe(channelsToUnsubscribe []subscription.Subscription) error { +func (g *Gateio) DeliveryFuturesUnsubscribe(channelsToUnsubscribe subscription.List) error { return g.handleDeliveryFuturesSubscription("unsubscribe", channelsToUnsubscribe) } // handleDeliveryFuturesSubscription sends a websocket message to receive data from the channel -func (g *Gateio) handleDeliveryFuturesSubscription(event string, channelsToSubscribe []subscription.Subscription) error { +func (g *Gateio) handleDeliveryFuturesSubscription(event string, channelsToSubscribe subscription.List) error { payloads, err := g.generateDeliveryFuturesPayload(event, channelsToSubscribe) if err != nil { return err @@ -222,16 +223,19 @@ func (g *Gateio) handleDeliveryFuturesSubscription(event string, channelsToSubsc errs = common.AppendError(errs, fmt.Errorf("error while %s to channel %s error code: %d message: %s", val[k].Event, val[k].Channel, resp.Error.Code, resp.Error.Message)) continue } - g.Websocket.AddSuccessfulSubscriptions(channelsToSubscribe[k]) + if err = g.Websocket.AddSuccessfulSubscriptions(channelsToSubscribe[k]); err != nil { + errs = common.AppendError(errs, err) + } } } } return errs } -func (g *Gateio) generateDeliveryFuturesPayload(event string, channelsToSubscribe []subscription.Subscription) ([2][]WsInput, error) { +func (g *Gateio) generateDeliveryFuturesPayload(event string, channelsToSubscribe subscription.List) ([2][]WsInput, error) { + payloads := [2][]WsInput{} if len(channelsToSubscribe) == 0 { - return [2][]WsInput{}, errors.New("cannot generate payload, no channels supplied") + return payloads, errors.New("cannot generate payload, no channels supplied") } var creds *account.Credentials var err error @@ -241,12 +245,14 @@ func (g *Gateio) generateDeliveryFuturesPayload(event string, channelsToSubscrib g.Websocket.SetCanUseAuthenticatedEndpoints(false) } } - payloads := [2][]WsInput{} for i := range channelsToSubscribe { + if len(channelsToSubscribe[i].Pairs) != 1 { + return payloads, subscription.ErrNotSinglePair + } var auth *WsAuthInput timestamp := time.Now() var params []string - params = []string{channelsToSubscribe[i].Pair.String()} + params = []string{channelsToSubscribe[i].Pairs[0].String()} if g.Websocket.CanUseAuthenticatedEndpoints() { switch channelsToSubscribe[i].Channel { case futuresOrdersChannel, futuresUserTradesChannel, @@ -256,9 +262,7 @@ func (g *Gateio) generateDeliveryFuturesPayload(event string, channelsToSubscrib futuresAutoOrdersChannel: value, ok := channelsToSubscribe[i].Params["user"].(string) if ok { - params = append( - []string{value}, - params...) + params = append([]string{value}, params...) } var sigTemp string sigTemp, err = g.generateWsSignature(creds.Secret, event, channelsToSubscribe[i].Channel, timestamp) @@ -310,7 +314,7 @@ func (g *Gateio) generateDeliveryFuturesPayload(event string, channelsToSubscrib params = append(params, intervalString) } } - if strings.HasPrefix(channelsToSubscribe[i].Pair.Quote.Upper().String(), "USDT") { + if strings.HasPrefix(channelsToSubscribe[i].Pairs[0].Quote.Upper().String(), "USDT") { payloads[0] = append(payloads[0], WsInput{ ID: g.Websocket.Conn.GenerateMessageID(false), Event: event, diff --git a/exchanges/gateio/gateio_ws_futures.go b/exchanges/gateio/gateio_ws_futures.go index 19af7e49d71..c24fbcf67fb 100644 --- a/exchanges/gateio/gateio_ws_futures.go +++ b/exchanges/gateio/gateio_ws_futures.go @@ -122,7 +122,7 @@ func (g *Gateio) WsFuturesConnect() error { } // GenerateFuturesDefaultSubscriptions returns default subscriptions information. -func (g *Gateio) GenerateFuturesDefaultSubscriptions() ([]subscription.Subscription, error) { +func (g *Gateio) GenerateFuturesDefaultSubscriptions() (subscription.List, error) { channelsToSubscribe := defaultFuturesSubscriptions if g.Websocket.CanUseAuthenticatedEndpoints() { channelsToSubscribe = append(channelsToSubscribe, @@ -135,7 +135,7 @@ func (g *Gateio) GenerateFuturesDefaultSubscriptions() ([]subscription.Subscript if err != nil { return nil, err } - subscriptions := make([]subscription.Subscription, len(channelsToSubscribe)*len(pairs)) + subscriptions := make(subscription.List, len(channelsToSubscribe)*len(pairs)) count := 0 for i := range channelsToSubscribe { for j := range pairs { @@ -154,9 +154,9 @@ func (g *Gateio) GenerateFuturesDefaultSubscriptions() ([]subscription.Subscript if err != nil { return nil, err } - subscriptions[count] = subscription.Subscription{ + subscriptions[count] = &subscription.Subscription{ Channel: channelsToSubscribe[i], - Pair: fpair.Upper(), + Pairs: currency.Pairs{fpair.Upper()}, Params: params, } count++ @@ -166,12 +166,12 @@ func (g *Gateio) GenerateFuturesDefaultSubscriptions() ([]subscription.Subscript } // FuturesSubscribe sends a websocket message to stop receiving data from the channel -func (g *Gateio) FuturesSubscribe(channelsToUnsubscribe []subscription.Subscription) error { +func (g *Gateio) FuturesSubscribe(channelsToUnsubscribe subscription.List) error { return g.handleFuturesSubscription("subscribe", channelsToUnsubscribe) } // FuturesUnsubscribe sends a websocket message to stop receiving data from the channel -func (g *Gateio) FuturesUnsubscribe(channelsToUnsubscribe []subscription.Subscription) error { +func (g *Gateio) FuturesUnsubscribe(channelsToUnsubscribe subscription.List) error { return g.handleFuturesSubscription("unsubscribe", channelsToUnsubscribe) } @@ -276,7 +276,7 @@ func (g *Gateio) wsHandleFuturesData(respRaw []byte, assetType asset.Item) error } // handleFuturesSubscription sends a websocket message to receive data from the channel -func (g *Gateio) handleFuturesSubscription(event string, channelsToSubscribe []subscription.Subscription) error { +func (g *Gateio) handleFuturesSubscription(event string, channelsToSubscribe subscription.List) error { payloads, err := g.generateFuturesPayload(event, channelsToSubscribe) if err != nil { return err @@ -303,7 +303,9 @@ func (g *Gateio) handleFuturesSubscription(event string, channelsToSubscribe []s errs = common.AppendError(errs, fmt.Errorf("error while %s to channel %s error code: %d message: %s", val[k].Event, val[k].Channel, resp.Error.Code, resp.Error.Message)) continue } - g.Websocket.AddSuccessfulSubscriptions(channelsToSubscribe[k]) + if err = g.Websocket.AddSuccessfulSubscriptions(channelsToSubscribe[k]); err != nil { + errs = common.AppendError(errs, err) + } } } } @@ -313,9 +315,10 @@ func (g *Gateio) handleFuturesSubscription(event string, channelsToSubscribe []s return nil } -func (g *Gateio) generateFuturesPayload(event string, channelsToSubscribe []subscription.Subscription) ([2][]WsInput, error) { +func (g *Gateio) generateFuturesPayload(event string, channelsToSubscribe subscription.List) ([2][]WsInput, error) { + payloads := [2][]WsInput{} if len(channelsToSubscribe) == 0 { - return [2][]WsInput{}, errors.New("cannot generate payload, no channels supplied") + return payloads, errors.New("cannot generate payload, no channels supplied") } var creds *account.Credentials var err error @@ -325,12 +328,14 @@ func (g *Gateio) generateFuturesPayload(event string, channelsToSubscribe []subs g.Websocket.SetCanUseAuthenticatedEndpoints(false) } } - payloads := [2][]WsInput{} for i := range channelsToSubscribe { + if len(channelsToSubscribe[i].Pairs) != 1 { + return payloads, subscription.ErrNotSinglePair + } var auth *WsAuthInput timestamp := time.Now() var params []string - params = []string{channelsToSubscribe[i].Pair.String()} + params = []string{channelsToSubscribe[i].Pairs[0].String()} if g.Websocket.CanUseAuthenticatedEndpoints() { switch channelsToSubscribe[i].Channel { case futuresOrdersChannel, futuresUserTradesChannel, @@ -394,7 +399,7 @@ func (g *Gateio) generateFuturesPayload(event string, channelsToSubscribe []subs params = append(params, intervalString) } } - if strings.HasPrefix(channelsToSubscribe[i].Pair.Quote.Upper().String(), "USDT") { + if strings.HasPrefix(channelsToSubscribe[i].Pairs[0].Quote.Upper().String(), "USDT") { payloads[0] = append(payloads[0], WsInput{ ID: g.Websocket.Conn.GenerateMessageID(false), Event: event, diff --git a/exchanges/gateio/gateio_ws_option.go b/exchanges/gateio/gateio_ws_option.go index 92518f43851..d69f5425541 100644 --- a/exchanges/gateio/gateio_ws_option.go +++ b/exchanges/gateio/gateio_ws_option.go @@ -105,7 +105,7 @@ func (g *Gateio) WsOptionsConnect() error { } // GenerateOptionsDefaultSubscriptions generates list of channel subscriptions for options asset type. -func (g *Gateio) GenerateOptionsDefaultSubscriptions() ([]subscription.Subscription, error) { +func (g *Gateio) GenerateOptionsDefaultSubscriptions() (subscription.List, error) { channelsToSubscribe := defaultOptionsSubscriptions var userID int64 if g.Websocket.CanUseAuthenticatedEndpoints() { @@ -130,7 +130,7 @@ func (g *Gateio) GenerateOptionsDefaultSubscriptions() ([]subscription.Subscript } } getEnabledPairs: - var subscriptions []subscription.Subscription + var subscriptions subscription.List pairs, err := g.GetEnabledPairs(asset.Options) if err != nil { return nil, err @@ -163,9 +163,9 @@ getEnabledPairs: if err != nil { return nil, err } - subscriptions = append(subscriptions, subscription.Subscription{ + subscriptions = append(subscriptions, &subscription.Subscription{ Channel: channelsToSubscribe[i], - Pair: fpair.Upper(), + Pairs: currency.Pairs{fpair.Upper()}, Params: params, }) } @@ -173,7 +173,7 @@ getEnabledPairs: return subscriptions, nil } -func (g *Gateio) generateOptionsPayload(event string, channelsToSubscribe []subscription.Subscription) ([]WsInput, error) { +func (g *Gateio) generateOptionsPayload(event string, channelsToSubscribe subscription.List) ([]WsInput, error) { if len(channelsToSubscribe) == 0 { return nil, errors.New("cannot generate payload, no channels supplied") } @@ -181,6 +181,9 @@ func (g *Gateio) generateOptionsPayload(event string, channelsToSubscribe []subs var intervalString string payloads := make([]WsInput, len(channelsToSubscribe)) for i := range channelsToSubscribe { + if len(channelsToSubscribe[i].Pairs) != 1 { + return nil, subscription.ErrNotSinglePair + } var auth *WsAuthInput timestamp := time.Now() var params []string @@ -190,7 +193,7 @@ func (g *Gateio) generateOptionsPayload(event string, channelsToSubscribe []subs optionsUnderlyingPriceChannel, optionsUnderlyingCandlesticksChannel: var uly currency.Pair - uly, err = g.GetUnderlyingFromCurrencyPair(channelsToSubscribe[i].Pair) + uly, err = g.GetUnderlyingFromCurrencyPair(channelsToSubscribe[i].Pairs[0]) if err != nil { return nil, err } @@ -198,8 +201,8 @@ func (g *Gateio) generateOptionsPayload(event string, channelsToSubscribe []subs case optionsBalancesChannel: // options.balance channel does not require underlying or contract default: - channelsToSubscribe[i].Pair.Delimiter = currency.UnderscoreDelimiter - params = append(params, channelsToSubscribe[i].Pair.String()) + channelsToSubscribe[i].Pairs[0].Delimiter = currency.UnderscoreDelimiter + params = append(params, channelsToSubscribe[i].Pairs[0].String()) } switch channelsToSubscribe[i].Channel { case optionsOrderbookChannel: @@ -299,17 +302,17 @@ func (g *Gateio) wsReadOptionsConnData() { } // OptionsSubscribe sends a websocket message to stop receiving data for asset type options -func (g *Gateio) OptionsSubscribe(channelsToUnsubscribe []subscription.Subscription) error { +func (g *Gateio) OptionsSubscribe(channelsToUnsubscribe subscription.List) error { return g.handleOptionsSubscription("subscribe", channelsToUnsubscribe) } // OptionsUnsubscribe sends a websocket message to stop receiving data for asset type options -func (g *Gateio) OptionsUnsubscribe(channelsToUnsubscribe []subscription.Subscription) error { +func (g *Gateio) OptionsUnsubscribe(channelsToUnsubscribe subscription.List) error { return g.handleOptionsSubscription("unsubscribe", channelsToUnsubscribe) } // handleOptionsSubscription sends a websocket message to receive data from the channel -func (g *Gateio) handleOptionsSubscription(event string, channelsToSubscribe []subscription.Subscription) error { +func (g *Gateio) handleOptionsSubscription(event string, channelsToSubscribe subscription.List) error { payloads, err := g.generateOptionsPayload(event, channelsToSubscribe) if err != nil { return err @@ -330,9 +333,12 @@ func (g *Gateio) handleOptionsSubscription(event string, channelsToSubscribe []s continue } if payloads[k].Event == "subscribe" { - g.Websocket.AddSuccessfulSubscriptions(channelsToSubscribe[k]) + err = g.Websocket.AddSuccessfulSubscriptions(channelsToSubscribe[k]) } else { - g.Websocket.RemoveSubscriptions(channelsToSubscribe[k]) + err = g.Websocket.RemoveSubscriptions(channelsToSubscribe[k]) + } + if err != nil { + errs = common.AppendError(errs, err) } } } diff --git a/exchanges/gemini/gemini_types.go b/exchanges/gemini/gemini_types.go index bcd18da1280..7766f13b8b6 100644 --- a/exchanges/gemini/gemini_types.go +++ b/exchanges/gemini/gemini_types.go @@ -336,8 +336,15 @@ type wsSubscriptions struct { Symbols []string `json:"symbols"` } +type wsSubOp string + +const ( + wsSubscribeOp wsSubOp = "subscribe" + wsUnsubscribeOp wsSubOp = "unsubscribe" +) + type wsSubscribeRequest struct { - Type string `json:"type"` + Type wsSubOp `json:"type"` Subscriptions []wsSubscriptions `json:"subscriptions"` } diff --git a/exchanges/gemini/gemini_websocket.go b/exchanges/gemini/gemini_websocket.go index ddfd73e2464..566611bce1a 100644 --- a/exchanges/gemini/gemini_websocket.go +++ b/exchanges/gemini/gemini_websocket.go @@ -13,7 +13,6 @@ import ( "time" "github.com/gorilla/websocket" - "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/common/crypto" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" @@ -63,7 +62,7 @@ func (g *Gemini) WsConnect() error { } // GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions() -func (g *Gemini) GenerateDefaultSubscriptions() ([]subscription.Subscription, error) { +func (g *Gemini) GenerateDefaultSubscriptions() (subscription.List, error) { // See gemini_types.go for more subscription/candle vars var channels = []string{ marketDataLevel2, @@ -75,105 +74,53 @@ func (g *Gemini) GenerateDefaultSubscriptions() ([]subscription.Subscription, er return nil, err } - var subscriptions []subscription.Subscription + var subscriptions subscription.List for x := range channels { - for y := range pairs { - subscriptions = append(subscriptions, subscription.Subscription{ - Channel: channels[x], - Pair: pairs[y], - Asset: asset.Spot, - }) - } + subscriptions = append(subscriptions, &subscription.Subscription{ + Channel: channels[x], + Pairs: pairs, + Asset: asset.Spot, + }) } return subscriptions, nil } // Subscribe sends a websocket message to receive data from the channel -func (g *Gemini) Subscribe(channelsToSubscribe []subscription.Subscription) error { - channels := make([]string, 0, len(channelsToSubscribe)) - for x := range channelsToSubscribe { - if common.StringDataCompareInsensitive(channels, channelsToSubscribe[x].Channel) { - continue - } - channels = append(channels, channelsToSubscribe[x].Channel) - } - - var pairs currency.Pairs - for x := range channelsToSubscribe { - if pairs.Contains(channelsToSubscribe[x].Pair, true) { - continue - } - pairs = append(pairs, channelsToSubscribe[x].Pair) - } - - fmtPairs, err := g.FormatExchangeCurrencies(pairs, asset.Spot) - if err != nil { - return err - } +func (g *Gemini) Subscribe(subs subscription.List) error { + return g.manageSubs(subs, wsSubscribeOp) +} - subs := make([]wsSubscriptions, len(channels)) - for x := range channels { - subs[x] = wsSubscriptions{ - Name: channels[x], - Symbols: strings.Split(fmtPairs, ","), - } - } +// Unsubscribe sends a websocket message to stop receiving data from the channel +func (g *Gemini) Unsubscribe(subs subscription.List) error { + return g.manageSubs(subs, wsUnsubscribeOp) +} - wsSub := wsSubscribeRequest{ - Type: "subscribe", - Subscriptions: subs, - } - err = g.Websocket.Conn.SendJSONMessage(wsSub) +func (g *Gemini) manageSubs(subs subscription.List, op wsSubOp) error { + format, err := g.GetPairFormat(asset.Spot, true) if err != nil { return err } - g.Websocket.AddSuccessfulSubscriptions(channelsToSubscribe...) - return nil -} - -// Unsubscribe sends a websocket message to stop receiving data from the channel -func (g *Gemini) Unsubscribe(channelsToUnsubscribe []subscription.Subscription) error { - channels := make([]string, 0, len(channelsToUnsubscribe)) - for x := range channelsToUnsubscribe { - if common.StringDataCompareInsensitive(channels, channelsToUnsubscribe[x].Channel) { - continue - } - channels = append(channels, channelsToUnsubscribe[x].Channel) + req := wsSubscribeRequest{ + Type: op, + Subscriptions: make([]wsSubscriptions, 0, len(subs)), } - - var pairs currency.Pairs - for x := range channelsToUnsubscribe { - if pairs.Contains(channelsToUnsubscribe[x].Pair, true) { - continue - } - pairs = append(pairs, channelsToUnsubscribe[x].Pair) + for _, s := range subs { + req.Subscriptions = append(req.Subscriptions, wsSubscriptions{ + Name: s.Channel, + Symbols: s.Pairs.Format(format).Strings(), + }) } - fmtPairs, err := g.FormatExchangeCurrencies(pairs, asset.Spot) - if err != nil { + if err := g.Websocket.Conn.SendJSONMessage(req); err != nil { return err } - subs := make([]wsSubscriptions, len(channels)) - for x := range channels { - subs[x] = wsSubscriptions{ - Name: channels[x], - Symbols: strings.Split(fmtPairs, ","), - } - } - - wsSub := wsSubscribeRequest{ - Type: "unsubscribe", - Subscriptions: subs, - } - err = g.Websocket.Conn.SendJSONMessage(wsSub) - if err != nil { - return err + if op == wsUnsubscribeOp { + return g.Websocket.RemoveSubscriptions(subs...) } - g.Websocket.RemoveSubscriptions(channelsToUnsubscribe...) - return nil + return g.Websocket.AddSuccessfulSubscriptions(subs...) } // WsAuth will connect to Gemini's secure endpoint diff --git a/exchanges/hitbtc/hitbtc_types.go b/exchanges/hitbtc/hitbtc_types.go index 4f55f206822..6ed9f5aaa47 100644 --- a/exchanges/hitbtc/hitbtc_types.go +++ b/exchanges/hitbtc/hitbtc_types.go @@ -292,24 +292,23 @@ type ResponseError struct { Message string `json:"message"` } -// WsRequest defines a request obj for the JSON-RPC and gets a websocket -// response +// WsRequest defines a request obj for the JSON-RPC and gets a websocket response type WsRequest struct { - Method string `json:"method"` - Params Params `json:"params,omitempty"` - ID int64 `json:"id"` + Method string `json:"method"` + Params WsParams `json:"params,omitempty"` + ID int64 `json:"id"` } // WsNotification defines a notification obj for the JSON-RPC this does not get // a websocket response type WsNotification struct { - JSONRPCVersion string `json:"jsonrpc,omitempty"` - Method string `json:"method"` - Params Params `json:"params"` + JSONRPCVersion string `json:"jsonrpc,omitempty"` + Method string `json:"method"` + Params WsParams `json:"params"` } -// Params is params -type Params struct { +// WsParams are websocket params for a request +type WsParams struct { Symbol string `json:"symbol,omitempty"` Period string `json:"period,omitempty"` Limit int64 `json:"limit,omitempty"` diff --git a/exchanges/hitbtc/hitbtc_websocket.go b/exchanges/hitbtc/hitbtc_websocket.go index 8c297170355..c361daaa2f5 100644 --- a/exchanges/hitbtc/hitbtc_websocket.go +++ b/exchanges/hitbtc/hitbtc_websocket.go @@ -466,33 +466,33 @@ func (h *HitBTC) WsProcessOrderbookUpdate(update *WsOrderbook) error { } // GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions() -func (h *HitBTC) GenerateDefaultSubscriptions() ([]subscription.Subscription, error) { - var channels = []string{"subscribeTicker", - "subscribeOrderbook", - "subscribeTrades", - "subscribeCandles"} +func (h *HitBTC) GenerateDefaultSubscriptions() (subscription.List, error) { + var channels = []string{ + "Ticker", + "Orderbook", + "Trades", + "Candles", + } - var subscriptions []subscription.Subscription + var subscriptions subscription.List if h.Websocket.CanUseAuthenticatedEndpoints() { - subscriptions = append(subscriptions, subscription.Subscription{ - Channel: "subscribeReports", - }) + subscriptions = append(subscriptions, &subscription.Subscription{Channel: "Reports"}) } - enabledCurrencies, err := h.GetEnabledPairs(asset.Spot) + pairs, err := h.GetEnabledPairs(asset.Spot) if err != nil { return nil, err } + pairFmt, err := h.GetPairFormat(asset.Spot, true) + if err != nil { + return nil, err + } + pairFmt.Delimiter = "" + pairs = pairs.Format(pairFmt) for i := range channels { - for j := range enabledCurrencies { - fPair, err := h.FormatExchangeCurrency(enabledCurrencies[j], asset.Spot) - if err != nil { - return nil, err - } - - enabledCurrencies[j].Delimiter = "" - subscriptions = append(subscriptions, subscription.Subscription{ + for j := range pairs { + subscriptions = append(subscriptions, &subscription.Subscription{ Channel: channels[i], - Pair: fPair, + Pairs: currency.Pairs{pairs[j]}, Asset: asset.Spot, }) } @@ -501,70 +501,74 @@ func (h *HitBTC) GenerateDefaultSubscriptions() ([]subscription.Subscription, er } // Subscribe sends a websocket message to receive data from the channel -func (h *HitBTC) Subscribe(channelsToSubscribe []subscription.Subscription) error { +func (h *HitBTC) Subscribe(channelsToSubscribe subscription.List) error { var errs error - for i := range channelsToSubscribe { - subscribe := WsRequest{ - Method: channelsToSubscribe[i].Channel, - ID: h.Websocket.Conn.GenerateMessageID(false), + for _, s := range channelsToSubscribe { + if len(s.Pairs) != 1 { + return subscription.ErrNotSinglePair } + pair := s.Pairs[0] - if channelsToSubscribe[i].Pair.String() != "" { - subscribe.Params.Symbol = channelsToSubscribe[i].Pair.String() + r := WsRequest{ + Method: "subscribe" + s.Channel, + ID: h.Websocket.Conn.GenerateMessageID(false), + Params: WsParams{ + Symbol: pair.String(), + }, } - if strings.EqualFold(channelsToSubscribe[i].Channel, "subscribeTrades") { - subscribe.Params.Limit = 100 - } else if strings.EqualFold(channelsToSubscribe[i].Channel, "subscribeCandles") { - subscribe.Params.Period = "M30" - subscribe.Params.Limit = 100 + switch s.Channel { + case "Trades": + r.Params.Limit = 100 + case "Candles": + r.Params.Period = "M30" + r.Params.Limit = 100 } - err := h.Websocket.Conn.SendJSONMessage(subscribe) + err := h.Websocket.Conn.SendJSONMessage(r) + if err == nil { + err = h.Websocket.AddSuccessfulSubscriptions(s) + } if err != nil { errs = common.AppendError(errs, err) - continue } - h.Websocket.AddSuccessfulSubscriptions(channelsToSubscribe[i]) } - if errs != nil { - return errs - } - return nil + return errs } // Unsubscribe sends a websocket message to stop receiving data from the channel -func (h *HitBTC) Unsubscribe(channelsToUnsubscribe []subscription.Subscription) error { +func (h *HitBTC) Unsubscribe(subs subscription.List) error { var errs error - for i := range channelsToUnsubscribe { - unsubscribeChannel := strings.Replace(channelsToUnsubscribe[i].Channel, - "subscribe", - "unsubscribe", - 1) + for _, s := range subs { + if len(s.Pairs) != 1 { + return subscription.ErrNotSinglePair + } + pair := s.Pairs[0] - unsubscribe := WsNotification{ + r := WsNotification{ JSONRPCVersion: rpcVersion, - Method: unsubscribeChannel, + Method: "unsubscribe" + s.Channel, + Params: WsParams{ + Symbol: pair.String(), + }, } - unsubscribe.Params.Symbol = channelsToUnsubscribe[i].Pair.String() - if strings.EqualFold(unsubscribeChannel, "unsubscribeTrades") { - unsubscribe.Params.Limit = 100 - } else if strings.EqualFold(unsubscribeChannel, "unsubscribeCandles") { - unsubscribe.Params.Period = "M30" - unsubscribe.Params.Limit = 100 + switch s.Channel { + case "Trades": + r.Params.Limit = 100 + case "Candles": + r.Params.Period = "M30" + r.Params.Limit = 100 } - err := h.Websocket.Conn.SendJSONMessage(unsubscribe) + err := h.Websocket.Conn.SendJSONMessage(r) + if err == nil { + err = h.Websocket.RemoveSubscriptions(s) + } if err != nil { errs = common.AppendError(errs, err) - continue } - h.Websocket.RemoveSubscriptions(channelsToUnsubscribe[i]) } - if errs != nil { - return errs - } - return nil + return errs } // Unsubscribe sends a websocket message to stop receiving data from the channel diff --git a/exchanges/huobi/huobi_websocket.go b/exchanges/huobi/huobi_websocket.go index d204e72a63f..4a4e4d7adf7 100644 --- a/exchanges/huobi/huobi_websocket.go +++ b/exchanges/huobi/huobi_websocket.go @@ -515,15 +515,15 @@ func (h *HUOBI) WsProcessOrderbook(update *WsDepth, symbol string) error { } // GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions() -func (h *HUOBI) GenerateDefaultSubscriptions() ([]subscription.Subscription, error) { +func (h *HUOBI) GenerateDefaultSubscriptions() (subscription.List, error) { var channels = []string{wsMarketKline, wsMarketDepth, wsMarketTrade, wsMarketTicker} - var subscriptions []subscription.Subscription + var subscriptions subscription.List if h.Websocket.CanUseAuthenticatedEndpoints() { channels = append(channels, "orders.%v", "orders.%v.update") - subscriptions = append(subscriptions, subscription.Subscription{ + subscriptions = append(subscriptions, &subscription.Subscription{ Channel: "accounts", }) } @@ -536,9 +536,9 @@ func (h *HUOBI) GenerateDefaultSubscriptions() ([]subscription.Subscription, err enabledCurrencies[j].Delimiter = "" channel := fmt.Sprintf(channels[i], enabledCurrencies[j].Lower().String()) - subscriptions = append(subscriptions, subscription.Subscription{ + subscriptions = append(subscriptions, &subscription.Subscription{ Channel: channel, - Pair: enabledCurrencies[j], + Pairs: currency.Pairs{enabledCurrencies[j]}, }) } } @@ -546,7 +546,7 @@ func (h *HUOBI) GenerateDefaultSubscriptions() ([]subscription.Subscription, err } // Subscribe sends a websocket message to receive data from the channel -func (h *HUOBI) Subscribe(channelsToSubscribe []subscription.Subscription) error { +func (h *HUOBI) Subscribe(channelsToSubscribe subscription.List) error { var creds *account.Credentials if h.Websocket.CanUseAuthenticatedEndpoints() { var err error @@ -557,36 +557,30 @@ func (h *HUOBI) Subscribe(channelsToSubscribe []subscription.Subscription) error } var errs error for i := range channelsToSubscribe { + var err error if (strings.Contains(channelsToSubscribe[i].Channel, "orders.") || strings.Contains(channelsToSubscribe[i].Channel, "accounts")) && creds != nil { - err := h.wsAuthenticatedSubscribe(creds, + err = h.wsAuthenticatedSubscribe(creds, "sub", wsAccountsOrdersEndPoint+channelsToSubscribe[i].Channel, channelsToSubscribe[i].Channel) - if err != nil { - errs = common.AppendError(errs, err) - continue - } - h.Websocket.AddSuccessfulSubscriptions(channelsToSubscribe[i]) - continue + } else { + err = h.Websocket.Conn.SendJSONMessage(WsRequest{ + Subscribe: channelsToSubscribe[i].Channel, + }) + } + if err == nil { + err = h.Websocket.AddSuccessfulSubscriptions(channelsToSubscribe[i]) } - err := h.Websocket.Conn.SendJSONMessage(WsRequest{ - Subscribe: channelsToSubscribe[i].Channel, - }) if err != nil { errs = common.AppendError(errs, err) - continue } - h.Websocket.AddSuccessfulSubscriptions(channelsToSubscribe[i]) - } - if errs != nil { - return errs } return nil } // Unsubscribe sends a websocket message to stop receiving data from the channel -func (h *HUOBI) Unsubscribe(channelsToUnsubscribe []subscription.Subscription) error { +func (h *HUOBI) Unsubscribe(channelsToUnsubscribe subscription.List) error { var creds *account.Credentials if h.Websocket.CanUseAuthenticatedEndpoints() { var err error @@ -597,32 +591,26 @@ func (h *HUOBI) Unsubscribe(channelsToUnsubscribe []subscription.Subscription) e } var errs error for i := range channelsToUnsubscribe { + var err error if (strings.Contains(channelsToUnsubscribe[i].Channel, "orders.") || strings.Contains(channelsToUnsubscribe[i].Channel, "accounts")) && creds != nil { - err := h.wsAuthenticatedSubscribe(creds, + err = h.wsAuthenticatedSubscribe(creds, "unsub", wsAccountsOrdersEndPoint+channelsToUnsubscribe[i].Channel, channelsToUnsubscribe[i].Channel) - if err != nil { - errs = common.AppendError(errs, err) - continue - } - h.Websocket.RemoveSubscriptions(channelsToUnsubscribe[i]) - continue + } else { + err = h.Websocket.Conn.SendJSONMessage(WsRequest{ + Unsubscribe: channelsToUnsubscribe[i].Channel, + }) + } + if err == nil { + err = h.Websocket.RemoveSubscriptions(channelsToUnsubscribe[i]) } - err := h.Websocket.Conn.SendJSONMessage(WsRequest{ - Unsubscribe: channelsToUnsubscribe[i].Channel, - }) if err != nil { errs = common.AppendError(errs, err) - continue } - h.Websocket.RemoveSubscriptions(channelsToUnsubscribe[i]) - } - if errs != nil { - return errs } - return nil + return errs } func (h *HUOBI) wsGenerateSignature(creds *account.Credentials, timestamp, endpoint string) ([]byte, error) { diff --git a/exchanges/interfaces.go b/exchanges/interfaces.go index 86e8778aab7..e76f9268f30 100644 --- a/exchanges/interfaces.go +++ b/exchanges/interfaces.go @@ -70,9 +70,9 @@ type IBotExchange interface { EnableRateLimiter() error GetServerTime(ctx context.Context, ai asset.Item) (time.Time, error) GetWebsocket() (*stream.Websocket, error) - SubscribeToWebsocketChannels(channels []subscription.Subscription) error - UnsubscribeToWebsocketChannels(channels []subscription.Subscription) error - GetSubscriptions() ([]subscription.Subscription, error) + SubscribeToWebsocketChannels(channels subscription.List) error + UnsubscribeToWebsocketChannels(channels subscription.List) error + GetSubscriptions() (subscription.List, error) FlushWebsocketChannels() error AuthenticateWebsocket(ctx context.Context) error GetOrderExecutionLimits(a asset.Item, cp currency.Pair) (order.MinMaxLevel, error) diff --git a/exchanges/kraken/kraken_test.go b/exchanges/kraken/kraken_test.go index 8fd641570bc..c55eac95d00 100644 --- a/exchanges/kraken/kraken_test.go +++ b/exchanges/kraken/kraken_test.go @@ -1216,10 +1216,10 @@ func setupWsTests(t *testing.T) { // TestWebsocketSubscribe tests returning a message with an id func TestWebsocketSubscribe(t *testing.T) { setupWsTests(t) - err := k.Subscribe([]subscription.Subscription{ + err := k.Subscribe(subscription.List{ { Channel: defaultSubscribedChannels[0], - Pair: currency.NewPairWithDelimiter("XBT", "USD", "/"), + Pairs: currency.Pairs{currency.NewPairWithDelimiter("XBT", "USD", "/")}, }, }) if err != nil { diff --git a/exchanges/kraken/kraken_types.go b/exchanges/kraken/kraken_types.go index dcd51b155d6..d59d75a77f3 100644 --- a/exchanges/kraken/kraken_types.go +++ b/exchanges/kraken/kraken_types.go @@ -500,11 +500,11 @@ type WithdrawStatusResponse struct { // WebsocketSubscriptionEventRequest handles WS subscription events type WebsocketSubscriptionEventRequest struct { - Event string `json:"event"` // subscribe - RequestID int64 `json:"reqid,omitempty"` // Optional, client originated ID reflected in response message. - Pairs []string `json:"pair,omitempty"` // Array of currency pairs (pair1,pair2,pair3). - Subscription WebsocketSubscriptionData `json:"subscription,omitempty"` - Channels []subscription.Subscription `json:"-"` // Keeps track of associated subscriptions in batched outgoings + Event string `json:"event"` // subscribe + RequestID int64 `json:"reqid,omitempty"` // Optional, client originated ID reflected in response message. + Pairs []string `json:"pair,omitempty"` // Array of currency pairs (pair1,pair2,pair3). + Subscription WebsocketSubscriptionData `json:"subscription,omitempty"` + Channels subscription.List `json:"-"` // Keeps track of associated subscriptions in batched outgoings } // WebsocketBaseEventRequest Just has an "event" property diff --git a/exchanges/kraken/kraken_websocket.go b/exchanges/kraken/kraken_websocket.go index d11dc2689a7..eedccc8ff49 100644 --- a/exchanges/kraken/kraken_websocket.go +++ b/exchanges/kraken/kraken_websocket.go @@ -809,7 +809,7 @@ func (k *Kraken) wsProcessOrderBook(channelData *WebsocketChannelData, data map[ } }(&subscription.Subscription{ Channel: krakenWsOrderbook, - Pair: outbound, + Pairs: currency.Pairs{outbound}, Asset: asset.Spot, }) return err @@ -1160,25 +1160,25 @@ func (k *Kraken) wsProcessCandles(channelData *WebsocketChannelData, data []inte } // GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions() -func (k *Kraken) GenerateDefaultSubscriptions() ([]subscription.Subscription, error) { +func (k *Kraken) GenerateDefaultSubscriptions() (subscription.List, error) { enabledPairs, err := k.GetEnabledPairs(asset.Spot) if err != nil { return nil, err } - var subscriptions []subscription.Subscription + var subscriptions subscription.List for i := range defaultSubscribedChannels { for j := range enabledPairs { enabledPairs[j].Delimiter = "/" - subscriptions = append(subscriptions, subscription.Subscription{ + subscriptions = append(subscriptions, &subscription.Subscription{ Channel: defaultSubscribedChannels[i], - Pair: enabledPairs[j], + Pairs: currency.Pairs{enabledPairs[j]}, Asset: asset.Spot, }) } } if k.Websocket.CanUseAuthenticatedEndpoints() { for i := range authenticatedChannels { - subscriptions = append(subscriptions, subscription.Subscription{ + subscriptions = append(subscriptions, &subscription.Subscription{ Channel: authenticatedChannels[i], }) } @@ -1187,7 +1187,7 @@ func (k *Kraken) GenerateDefaultSubscriptions() ([]subscription.Subscription, er } // Subscribe sends a websocket message to receive data from the channel -func (k *Kraken) Subscribe(channelsToSubscribe []subscription.Subscription) error { +func (k *Kraken) Subscribe(channelsToSubscribe subscription.List) error { var subscriptions = make(map[string]*[]WebsocketSubscriptionEventRequest) channels: for i := range channelsToSubscribe { @@ -1198,7 +1198,7 @@ channels: } for j := range *s { - (*s)[j].Pairs = append((*s)[j].Pairs, channelsToSubscribe[i].Pair.String()) + (*s)[j].Pairs = append((*s)[j].Pairs, channelsToSubscribe[i].Pairs.Strings()...) (*s)[j].Channels = append((*s)[j].Channels, channelsToSubscribe[i]) continue channels } @@ -1214,8 +1214,8 @@ channels: if channelsToSubscribe[i].Channel == "book" { outbound.Subscription.Depth = krakenWsOrderbookDepth } - if !channelsToSubscribe[i].Pair.IsEmpty() { - outbound.Pairs = []string{channelsToSubscribe[i].Pair.String()} + for _, p := range channelsToSubscribe[i].Pairs { + outbound.Pairs = append(outbound.Pairs, p.String()) } if common.StringDataContains(authenticatedChannels, channelsToSubscribe[i].Channel) { outbound.Subscription.Token = authToken @@ -1228,37 +1228,32 @@ channels: var errs error for _, subs := range subscriptions { for i := range *subs { + var err error if common.StringDataContains(authenticatedChannels, (*subs)[i].Subscription.Name) { - _, err := k.Websocket.AuthConn.SendMessageReturnResponse((*subs)[i].RequestID, (*subs)[i]) - if err != nil { - errs = common.AppendError(errs, err) - continue - } - k.Websocket.AddSuccessfulSubscriptions((*subs)[i].Channels...) - continue + _, err = k.Websocket.AuthConn.SendMessageReturnResponse((*subs)[i].RequestID, (*subs)[i]) + } else { + _, err = k.Websocket.Conn.SendMessageReturnResponse((*subs)[i].RequestID, (*subs)[i]) + } + if err == nil { + err = k.Websocket.AddSuccessfulSubscriptions((*subs)[i].Channels...) } - _, err := k.Websocket.Conn.SendMessageReturnResponse((*subs)[i].RequestID, (*subs)[i]) if err != nil { errs = common.AppendError(errs, err) - continue } - k.Websocket.AddSuccessfulSubscriptions((*subs)[i].Channels...) } } return errs } // Unsubscribe sends a websocket message to stop receiving data from the channel -func (k *Kraken) Unsubscribe(channelsToUnsubscribe []subscription.Subscription) error { +func (k *Kraken) Unsubscribe(channelsToUnsubscribe subscription.List) error { var unsubs []WebsocketSubscriptionEventRequest channels: for x := range channelsToUnsubscribe { for y := range unsubs { if unsubs[y].Subscription.Name == channelsToUnsubscribe[x].Channel { - unsubs[y].Pairs = append(unsubs[y].Pairs, - channelsToUnsubscribe[x].Pair.String()) - unsubs[y].Channels = append(unsubs[y].Channels, - channelsToUnsubscribe[x]) + unsubs[y].Pairs = append(unsubs[y].Pairs, channelsToUnsubscribe[x].Pairs.Strings()...) + unsubs[y].Channels = append(unsubs[y].Channels, channelsToUnsubscribe[x]) continue channels } } @@ -1276,7 +1271,7 @@ channels: unsub := WebsocketSubscriptionEventRequest{ Event: krakenWsUnsubscribe, - Pairs: []string{channelsToUnsubscribe[x].Pair.String()}, + Pairs: []string{channelsToUnsubscribe[x].Pairs[0].String()}, Subscription: WebsocketSubscriptionData{ Name: channelsToUnsubscribe[x].Channel, Depth: depth, @@ -1292,22 +1287,18 @@ channels: var errs error for i := range unsubs { + var err error if common.StringDataContains(authenticatedChannels, unsubs[i].Subscription.Name) { - _, err := k.Websocket.AuthConn.SendMessageReturnResponse(unsubs[i].RequestID, unsubs[i]) - if err != nil { - errs = common.AppendError(errs, err) - continue - } - k.Websocket.RemoveSubscriptions(unsubs[i].Channels...) - continue + _, err = k.Websocket.AuthConn.SendMessageReturnResponse(unsubs[i].RequestID, unsubs[i]) + } else { + _, err = k.Websocket.Conn.SendMessageReturnResponse(unsubs[i].RequestID, unsubs[i]) + } + if err == nil { + err = k.Websocket.RemoveSubscriptions(unsubs[i].Channels...) } - - _, err := k.Websocket.Conn.SendMessageReturnResponse(unsubs[i].RequestID, unsubs[i]) if err != nil { errs = common.AppendError(errs, err) - continue } - k.Websocket.RemoveSubscriptions(unsubs[i].Channels...) } return errs } diff --git a/exchanges/kucoin/kucoin_test.go b/exchanges/kucoin/kucoin_test.go index 8d7d9a7abda..5b88b128954 100644 --- a/exchanges/kucoin/kucoin_test.go +++ b/exchanges/kucoin/kucoin_test.go @@ -15,7 +15,6 @@ import ( "github.com/stretchr/testify/require" "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/common/key" - "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/core" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" @@ -26,7 +25,6 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/margin" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues" - "github.com/thrasher-corp/gocryptotrader/exchanges/stream/buffer" "github.com/thrasher-corp/gocryptotrader/exchanges/subscription" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" testexch "github.com/thrasher-corp/gocryptotrader/internal/testing/exchange" @@ -41,45 +39,27 @@ const ( canManipulateRealOrders = false ) -var ( - ku = &Kucoin{} - spotTradablePair, marginTradablePair, futuresTradablePair currency.Pair -) +var ku *Kucoin +var spotTradablePair, marginTradablePair, futuresTradablePair currency.Pair func TestMain(m *testing.M) { - ku.SetDefaults() - cfg := config.GetConfig() - err := cfg.LoadConfig("../../testdata/configtest.json", true) - if err != nil { - log.Fatal(err) - } - - exchCfg, err := cfg.GetExchangeConfig("Kucoin") - if err != nil { + ku = new(Kucoin) + if err := testexch.Setup(ku); err != nil { log.Fatal(err) } - exchCfg.API.AuthenticatedSupport = true - exchCfg.API.AuthenticatedWebsocketSupport = true - - exchCfg.API.Credentials.Key = apiKey - exchCfg.API.Credentials.Secret = apiSecret - exchCfg.API.Credentials.ClientID = passPhrase if apiKey != "" && apiSecret != "" && passPhrase != "" { + ku.API.AuthenticatedSupport = true + ku.API.AuthenticatedWebsocketSupport = true + ku.API.CredentialsValidator.RequiresBase64DecodeSecret = false + ku.SetCredentials(apiKey, apiSecret, passPhrase, "", "", "") ku.Websocket.SetCanUseAuthenticatedEndpoints(true) } - ku.SetDefaults() - ku.Websocket = sharedtestvalues.NewTestWebsocket() - ku.Websocket.Orderbook = buffer.Orderbook{} - err = ku.Setup(exchCfg) - if err != nil { - log.Fatal(err) - } - ku.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride() - ku.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride() - setupWS() getFirstTradablePairOfAssets() + ku.setupOrderbookManager() + fetchedFuturesSnapshotOrderbook = map[string]bool{} + os.Exit(m.Run()) } @@ -1993,10 +1973,10 @@ func TestPushData(t *testing.T) { testexch.FixtureToDataHandler(t, "testdata/wsHandleData.json", ku.wsHandleData) } -func verifySubs(tb testing.TB, subs []subscription.Subscription, a asset.Item, prefix string, expected ...string) { +func verifySubs(tb testing.TB, subs subscription.List, a asset.Item, prefix string, expected ...string) { tb.Helper() var sub *subscription.Subscription - for i, s := range subs { //nolint:gocritic // prefer convenience over performance here for tests + for i, s := range subs { if s.Asset == a && strings.HasPrefix(s.Channel, prefix) { if len(expected) == 1 && !strings.Contains(s.Channel, expected[0]) { continue @@ -2005,7 +1985,7 @@ func verifySubs(tb testing.TB, subs []subscription.Subscription, a asset.Item, p assert.Failf(tb, "Too many subs with prefix", "Asset %s; Prefix %s", a.String(), prefix) return } - sub = &subs[i] + sub = subs[i] } } if assert.NotNil(tb, sub, "Should find a sub for asset %s with prefix %s for %s", a.String(), prefix, strings.Join(expected, ", ")) { @@ -2024,12 +2004,11 @@ func verifySubs(tb testing.TB, subs []subscription.Subscription, a asset.Item, p // In Both: ETH-BTC, LTC-USDT // Only in Margin: TRX-BTC, SOL-USDC -func TestGenerateDefaultSubscriptions(t *testing.T) { +func TestGenerateSubscriptions(t *testing.T) { t.Parallel() - subs, err := ku.GenerateDefaultSubscriptions() - - assert.NoError(t, err, "GenerateDefaultSubscriptions should not error") + subs, err := ku.generateSubscriptions() + require.NoError(t, err, "generateSubscriptions must not error") assert.Len(t, subs, 11, "Should generate the correct number of subs when not logged in") @@ -2053,8 +2032,8 @@ func TestGenerateAuthSubscriptions(t *testing.T) { ku := testInstance(t) //nolint:govet // Intentional shadow to avoid future copy/paste mistakes ku.Websocket.SetCanUseAuthenticatedEndpoints(true) - subs, err := ku.GenerateDefaultSubscriptions() - assert.NoError(t, err, "GenerateDefaultSubscriptions with Auth should not error") + subs, err := ku.generateSubscriptions() + require.NoError(t, err, "generateSubscriptions with Auth must not error") assert.Len(t, subs, 24, "Should generate the correct number of subs when logged in") verifySubs(t, subs, asset.Spot, "/market/ticker:all") // This takes care of margin as well. @@ -2084,12 +2063,12 @@ func TestGenerateCandleSubscription(t *testing.T) { t.Parallel() ku := testInstance(t) //nolint:govet // Intentional shadow to avoid future copy/paste mistakes - ku.Features.Subscriptions = []*subscription.Subscription{ + ku.Features.Subscriptions = subscription.List{ {Channel: subscription.CandlesChannel, Interval: kline.FourHour}, } - subs, err := ku.GenerateDefaultSubscriptions() - assert.NoError(t, err, "GenerateDefaultSubscriptions with Candles should not error") + subs, err := ku.generateSubscriptions() + assert.NoError(t, err, "generateSubscriptions with Candles should not error") assert.Len(t, subs, 6, "Should generate the correct number of subs for candles") for _, c := range []string{"BTC-USDT", "ETH-USDT", "LTC-USDT", "ETH-BTC"} { @@ -2104,12 +2083,12 @@ func TestGenerateMarketSubscription(t *testing.T) { t.Parallel() ku := testInstance(t) //nolint:govet // Intentional shadow to avoid future copy/paste mistakes - ku.Features.Subscriptions = []*subscription.Subscription{ + ku.Features.Subscriptions = subscription.List{ {Channel: marketSnapshotChannel}, } - subs, err := ku.GenerateDefaultSubscriptions() - assert.NoError(t, err, "GenerateDefaultSubscriptions with MarketSnapshot should not error") + subs, err := ku.generateSubscriptions() + assert.NoError(t, err, "generateSubscriptions with MarketSnapshot should not error") assert.Len(t, subs, 7, "Should generate the correct number of subs for snapshot") for _, c := range []string{"BTC", "ETH", "LTC", "USDT"} { @@ -2370,19 +2349,6 @@ func TestGetPaginatedListOfSubAccounts(t *testing.T) { } } -func setupWS() { - if !ku.Websocket.IsEnabled() { - return - } - if !sharedtestvalues.AreAPICredentialsSet(ku) { - ku.Websocket.SetCanUseAuthenticatedEndpoints(false) - } - err := ku.WsConnect() - if err != nil { - log.Fatal(err) - } -} - func TestGetFundingHistory(t *testing.T) { t.Parallel() sharedtestvalues.SkipTestIfCredentialsUnset(t, ku) @@ -2526,8 +2492,11 @@ func TestProcessMarketSnapshot(t *testing.T) { func TestSubscribeMarketSnapshot(t *testing.T) { t.Parallel() - setupWS() - err := ku.Subscribe([]subscription.Subscription{{Channel: marketSymbolSnapshotChannel, Pair: currency.Pair{Base: currency.BTC}}}) + + ku := testInstance(t) //nolint:govet // Intentional shadow to avoid future copy/paste mistakes + testexch.SetupWs(t, ku) + + err := ku.Subscribe(subscription.List{{Channel: marketSymbolSnapshotChannel, Pairs: currency.Pairs{currency.Pair{Base: currency.BTC}}}}) assert.NoError(t, err, "Subscribe to MarketSnapshot should not error") } diff --git a/exchanges/kucoin/kucoin_websocket.go b/exchanges/kucoin/kucoin_websocket.go index a11aba0def0..5b7c14bde48 100644 --- a/exchanges/kucoin/kucoin_websocket.go +++ b/exchanges/kucoin/kucoin_websocket.go @@ -950,49 +950,50 @@ func (ku *Kucoin) processMarketSnapshot(respData []byte, topic string) error { } // Subscribe sends a websocket message to receive data from the channel -func (ku *Kucoin) Subscribe(subscriptions []subscription.Subscription) error { - return ku.handleSubscriptions(subscriptions, "subscribe") +func (ku *Kucoin) Subscribe(subscriptions subscription.List) error { + return ku.manageSubscriptions(subscriptions, "subscribe") } // Unsubscribe sends a websocket message to stop receiving data from the channel -func (ku *Kucoin) Unsubscribe(subscriptions []subscription.Subscription) error { - return ku.handleSubscriptions(subscriptions, "unsubscribe") +func (ku *Kucoin) Unsubscribe(subscriptions subscription.List) error { + return ku.manageSubscriptions(subscriptions, "unsubscribe") } -func (ku *Kucoin) expandManualSubscriptions(in []subscription.Subscription) ([]subscription.Subscription, error) { - subs := make([]subscription.Subscription, 0, len(in)) - for i := range in { - if isSymbolChannel(in[i].Channel) { - if in[i].Pair.IsEmpty() { +// expandManualSubscription takes a subscription list and expand all the subscriptions across the relevant assets and pairs +func (ku *Kucoin) expandManualSubscriptions(in subscription.List) (subscription.List, error) { + subs := make(subscription.List, 0, len(in)) + for _, s := range in { + if isSymbolChannel(s.Channel) { + if len(s.Pairs) == 0 { return nil, errSubscriptionPairRequired } - a := in[i].Asset + a := s.Asset if !a.IsValid() { - a = getChannelsAssetType(in[i].Channel) + a = getChannelsAssetType(s.Channel) } - assetPairs := map[asset.Item]currency.Pairs{a: {in[i].Pair}} - n, err := ku.expandSubscription(&in[i], assetPairs) + assetPairs := map[asset.Item]currency.Pairs{a: s.Pairs} + n, err := ku.expandSubscription(s, assetPairs) if err != nil { return nil, err } subs = append(subs, n...) } else { - subs = append(subs, in[i]) + subs = append(subs, s) } } return subs, nil } -func (ku *Kucoin) handleSubscriptions(subs []subscription.Subscription, operation string) error { +func (ku *Kucoin) manageSubscriptions(subs subscription.List, operation string) error { var errs error subs, errs = ku.expandManualSubscriptions(subs) - for i := range subs { + for _, s := range subs { msgID := strconv.FormatInt(ku.Websocket.Conn.GenerateMessageID(false), 10) req := WsSubscriptionInput{ ID: msgID, Type: operation, - Topic: subs[i].Channel, - PrivateChannel: subs[i].Authenticated, + Topic: s.Channel, + PrivateChannel: s.Authenticated, Response: true, } if respRaw, err := ku.Websocket.Conn.SendMessageReturnResponse("msgID:"+msgID, req); err != nil { @@ -1005,9 +1006,16 @@ func (ku *Kucoin) handleSubscriptions(subs []subscription.Subscription, operatio case rType != "ack": errs = common.AppendError(errs, fmt.Errorf("%w: %s from %s", errInvalidMsgType, rType, respRaw)) default: - ku.Websocket.AddSuccessfulSubscriptions(subs[i]) - if ku.Verbose { - log.Debugf(log.ExchangeSys, "%s Subscribed to Channel: %s", ku.Name, subs[i].Channel) + if operation == "unsubscribe" { + err = ku.Websocket.RemoveSubscriptions(s) + } else { + err = ku.Websocket.AddSuccessfulSubscriptions(s) + if ku.Verbose { + log.Debugf(log.ExchangeSys, "%s Subscribed to Channel: %s", ku.Name, s.Channel) + } + } + if err != nil { + errs = common.AppendError(errs, err) } } } @@ -1034,8 +1042,8 @@ func getChannelsAssetType(channelName string) asset.Item { } } -// GenerateDefaultSubscriptions Adds default subscriptions to websocket. -func (ku *Kucoin) GenerateDefaultSubscriptions() ([]subscription.Subscription, error) { +// generateSubscriptions returns a list of subscriptions from the configured subscriptions feature +func (ku *Kucoin) generateSubscriptions() (subscription.List, error) { assetPairs := map[asset.Item]currency.Pairs{} for _, a := range ku.GetAssetTypes(false) { if p, err := ku.GetEnabledPairs(a); err == nil { @@ -1045,7 +1053,7 @@ func (ku *Kucoin) GenerateDefaultSubscriptions() ([]subscription.Subscription, e } } authed := ku.Websocket.CanUseAuthenticatedEndpoints() - subscriptions := []subscription.Subscription{} + subscriptions := subscription.List{} for _, s := range ku.Features.Subscriptions { if !authed && s.Authenticated { continue @@ -1060,12 +1068,12 @@ func (ku *Kucoin) GenerateDefaultSubscriptions() ([]subscription.Subscription, e } // expandSubscription takes a subscription and expands it across the relevant assets and pairs passed in -func (ku *Kucoin) expandSubscription(baseSub *subscription.Subscription, assetPairs map[asset.Item]currency.Pairs) ([]subscription.Subscription, error) { - var subscriptions = []subscription.Subscription{} +func (ku *Kucoin) expandSubscription(baseSub *subscription.Subscription, assetPairs map[asset.Item]currency.Pairs) (subscription.List, error) { + var subscriptions = subscription.List{} if baseSub == nil { return nil, common.ErrNilPointer } - s := *baseSub + s := baseSub.Clone() s.Channel = channelName(s.Channel) if !s.Asset.IsValid() { s.Asset = getChannelsAssetType(s.Channel) @@ -1078,7 +1086,7 @@ func (ku *Kucoin) expandSubscription(baseSub *subscription.Subscription, assetPa switch { case s.Channel == marginLoanChannel: for _, c := range assetPairs[asset.Margin].GetCurrencies() { - i := s + i := s.Clone() i.Channel = fmt.Sprintf(s.Channel, c) subscriptions = append(subscriptions, i) } @@ -1087,13 +1095,13 @@ func (ku *Kucoin) expandSubscription(baseSub *subscription.Subscription, assetPa if err != nil { return nil, err } - subs := spotOrMarginPairSubs(assetPairs, &s, false, interval) + subs := spotOrMarginPairSubs(assetPairs, s, false, interval) subscriptions = append(subscriptions, subs...) case s.Channel == marginFundingbookChangeChannel: s.Channel = fmt.Sprintf(s.Channel, assetPairs[asset.Margin].GetCurrencies().Join()) subscriptions = append(subscriptions, s) case s.Channel == marketSnapshotChannel: - subs, err := spotOrMarginCurrencySubs(assetPairs, &s) + subs, err := spotOrMarginCurrencySubs(assetPairs, s) if err != nil { return nil, err } @@ -1104,13 +1112,13 @@ func (ku *Kucoin) expandSubscription(baseSub *subscription.Subscription, assetPa if err != nil { continue } - i := s + i := s.Clone() i.Channel = fmt.Sprintf(s.Channel, c) subscriptions = append(subscriptions, i) } case isSymbolChannel(s.Channel): // Subscriptions which can use a single comma-separated sub per asset - subs := spotOrMarginPairSubs(assetPairs, &s, true) + subs := spotOrMarginPairSubs(assetPairs, s, true) subscriptions = append(subscriptions, subs...) default: subscriptions = append(subscriptions, s) @@ -1135,21 +1143,23 @@ func channelName(name string) string { // spotOrMarginPairSubs accepts a map of pairs and a template subscription and returns a list of subscriptions for Spot and Margin pairs // If there's a Spot subscription, it won't be added again as a Margin subscription // If joined param is true then one subscription per asset type with the currencies comma delimited -func spotOrMarginPairSubs(assetPairs map[asset.Item]currency.Pairs, b *subscription.Subscription, join bool, fmtArgs ...any) []subscription.Subscription { - subs := []subscription.Subscription{} +func spotOrMarginPairSubs(assetPairs map[asset.Item]currency.Pairs, b *subscription.Subscription, join bool, fmtArgs ...any) subscription.List { + subs := subscription.List{} add := func(a asset.Item, pairs currency.Pairs) { if len(pairs) == 0 { return } - s := *b - s.Asset = a if join { f := append([]any{pairs.Join()}, fmtArgs...) + s := b.Clone() + s.Asset = a s.Channel = fmt.Sprintf(b.Channel, f...) subs = append(subs, s) } else { for i := range pairs { f := append([]any{pairs[i].String()}, fmtArgs...) + s := b.Clone() + s.Asset = a s.Channel = fmt.Sprintf(b.Channel, f...) subs = append(subs, s) } @@ -1171,18 +1181,18 @@ func spotOrMarginPairSubs(assetPairs map[asset.Item]currency.Pairs, b *subscript // spotOrMarginCurrencySubs accepts a map of pairs and a template subscription and returns a list of subscriptions for every currency in Spot and Margin pairs // If there's a Spot subscription, it won't be added again as a Margin subscription -func spotOrMarginCurrencySubs(assetPairs map[asset.Item]currency.Pairs, b *subscription.Subscription) ([]subscription.Subscription, error) { +func spotOrMarginCurrencySubs(assetPairs map[asset.Item]currency.Pairs, b *subscription.Subscription) (subscription.List, error) { if b == nil { return nil, common.ErrNilPointer } - subs := []subscription.Subscription{} + subs := subscription.List{} add := func(a asset.Item, currs currency.Currencies) { if len(currs) == 0 { return } - s := *b - s.Asset = a for _, c := range currs { + s := b.Clone() + s.Asset = a s.Channel = fmt.Sprintf(b.Channel, c) subs = append(subs, s) } diff --git a/exchanges/kucoin/kucoin_wrapper.go b/exchanges/kucoin/kucoin_wrapper.go index cc6849ae3f8..5e2899ddfa3 100644 --- a/exchanges/kucoin/kucoin_wrapper.go +++ b/exchanges/kucoin/kucoin_wrapper.go @@ -141,7 +141,7 @@ func (ku *Kucoin) SetDefaults() { GlobalResultLimit: 1500, }, }, - Subscriptions: []*subscription.Subscription{ + Subscriptions: subscription.List{ // Where we can we use generic names {Enabled: true, Channel: subscription.TickerChannel}, // marketTickerChannel {Enabled: true, Channel: subscription.AllTradesChannel}, // marketMatchChannel @@ -206,7 +206,7 @@ func (ku *Kucoin) Setup(exch *config.Exchange) error { Connector: ku.WsConnect, Subscriber: ku.Subscribe, Unsubscriber: ku.Unsubscribe, - GenerateSubscriptions: ku.GenerateDefaultSubscriptions, + GenerateSubscriptions: ku.generateSubscriptions, Features: &ku.Features.Supports.WebsocketCapabilities, OrderbookBufferConfig: buffer.Config{ SortBuffer: true, diff --git a/exchanges/okcoin/okcoin_websocket.go b/exchanges/okcoin/okcoin_websocket.go index 7c6f6ee1679..b5af0f29b83 100644 --- a/exchanges/okcoin/okcoin_websocket.go +++ b/exchanges/okcoin/okcoin_websocket.go @@ -582,9 +582,9 @@ func (o *Okcoin) wsProcessOrderbook(respRaw []byte, obChannel string) error { // ReSubscribeSpecificOrderbook removes the subscription and the subscribes // again to fetch a new snapshot in the event of a de-sync event. func (o *Okcoin) ReSubscribeSpecificOrderbook(obChannel string, p currency.Pair) error { - subscription := []subscription.Subscription{{ + subscription := subscription.List{{ Channel: obChannel, - Pair: p, + Pairs: currency.Pairs{p}, }} if err := o.Unsubscribe(subscription); err != nil { return err @@ -764,8 +764,8 @@ func (o *Okcoin) CalculateOrderbookUpdateChecksum(orderbookData *orderbook.Base) // GenerateDefaultSubscriptions Adds default subscriptions to websocket to be // handled by ManageSubscriptions() -func (o *Okcoin) GenerateDefaultSubscriptions() ([]subscription.Subscription, error) { - var subscriptions []subscription.Subscription +func (o *Okcoin) GenerateDefaultSubscriptions() (subscription.List, error) { + var subscriptions subscription.List pairs, err := o.GetEnabledPairs(asset.Spot) if err != nil { return nil, err @@ -788,7 +788,7 @@ func (o *Okcoin) GenerateDefaultSubscriptions() ([]subscription.Subscription, er for s := range channels { switch channels[s] { case wsInstruments: - subscriptions = append(subscriptions, subscription.Subscription{ + subscriptions = append(subscriptions, &subscription.Subscription{ Channel: channels[s], Asset: asset.Spot, }) @@ -799,20 +799,20 @@ func (o *Okcoin) GenerateDefaultSubscriptions() ([]subscription.Subscription, er wsCandle5m, wsCandle3m, wsCandle1m, wsCandle3Mutc, wsCandle1Mutc, wsCandle1Wutc, wsCandle1Dutc, wsCandle2Dutc, wsCandle3Dutc, wsCandle5Dutc, wsCandle12Hutc, wsCandle6Hutc: for p := range pairs { - subscriptions = append(subscriptions, subscription.Subscription{ + subscriptions = append(subscriptions, &subscription.Subscription{ Channel: channels[s], - Pair: pairs[p], + Pairs: currency.Pairs{pairs[p]}, }) } case wsStatus: - subscriptions = append(subscriptions, subscription.Subscription{ + subscriptions = append(subscriptions, &subscription.Subscription{ Channel: channels[s], }) case wsAccount: currenciesMap := map[currency.Code]bool{} for p := range pairs { if reserved, okay := currenciesMap[pairs[p].Base]; !okay && !reserved { - subscriptions = append(subscriptions, subscription.Subscription{ + subscriptions = append(subscriptions, &subscription.Subscription{ Channel: channels[s], Params: map[string]interface{}{ "ccy": pairs[p].Base, @@ -823,7 +823,7 @@ func (o *Okcoin) GenerateDefaultSubscriptions() ([]subscription.Subscription, er } for p := range pairs { if reserved, okay := currenciesMap[pairs[p].Quote]; !okay && !reserved { - subscriptions = append(subscriptions, subscription.Subscription{ + subscriptions = append(subscriptions, &subscription.Subscription{ Channel: channels[s], Params: map[string]interface{}{ "ccy": pairs[p].Quote, @@ -834,9 +834,9 @@ func (o *Okcoin) GenerateDefaultSubscriptions() ([]subscription.Subscription, er } case wsOrder, wsOrdersAlgo, wsAlgoAdvance: for p := range pairs { - subscriptions = append(subscriptions, subscription.Subscription{ + subscriptions = append(subscriptions, &subscription.Subscription{ Channel: channels[s], - Pair: pairs[p], + Pairs: currency.Pairs{pairs[p]}, Asset: asset.Spot, }) } @@ -848,23 +848,23 @@ func (o *Okcoin) GenerateDefaultSubscriptions() ([]subscription.Subscription, er } // Subscribe sends a websocket message to receive data from the channel -func (o *Okcoin) Subscribe(channelsToSubscribe []subscription.Subscription) error { - return o.handleSubscriptions("subscribe", channelsToSubscribe) +func (o *Okcoin) Subscribe(channelsToSubscribe subscription.List) error { + return o.manageSubscriptions("subscribe", channelsToSubscribe) } // Unsubscribe sends a websocket message to stop receiving data from the channel -func (o *Okcoin) Unsubscribe(channelsToUnsubscribe []subscription.Subscription) error { - return o.handleSubscriptions("unsubscribe", channelsToUnsubscribe) +func (o *Okcoin) Unsubscribe(channelsToUnsubscribe subscription.List) error { + return o.manageSubscriptions("unsubscribe", channelsToUnsubscribe) } -func (o *Okcoin) handleSubscriptions(operation string, subs []subscription.Subscription) error { +func (o *Okcoin) manageSubscriptions(operation string, subs subscription.List) error { subscriptionRequest := WebsocketEventRequest{Operation: operation, Arguments: []map[string]string{}} authRequest := WebsocketEventRequest{Operation: operation, Arguments: []map[string]string{}} temp := WebsocketEventRequest{Operation: operation, Arguments: []map[string]string{}} authTemp := WebsocketEventRequest{Operation: operation, Arguments: []map[string]string{}} var err error - var channels []subscription.Subscription - var authChannels []subscription.Subscription + var channels subscription.List + var authChannels subscription.List for i := 0; i < len(subs); i++ { authenticatedChannelSubscription := isAuthenticatedChannel(subs[i].Channel) // Temp type to evaluate max byte len after a marshal on batched unsubs @@ -891,8 +891,11 @@ func (o *Okcoin) handleSubscriptions(operation string, subs []subscription.Subsc if subs[i].Asset != asset.Empty { argument["instType"] = strings.ToUpper(subs[i].Asset.String()) } - if !subs[i].Pair.IsEmpty() { - argument["instId"] = subs[i].Pair.String() + if len(subs[i].Pairs) > 1 { + return subscription.ErrNotSinglePair + } + if len(subs[i].Pairs) == 1 { + argument["instId"] = subs[i].Pairs[0].String() } if authenticatedChannelSubscription { authTemp.Arguments = append(authTemp.Arguments, argument) @@ -928,17 +931,20 @@ func (o *Okcoin) handleSubscriptions(operation string, subs []subscription.Subsc if operation == "unsubscribe" { if authenticatedChannelSubscription { - o.Websocket.RemoveSubscriptions(authChannels...) + err = o.Websocket.RemoveSubscriptions(authChannels...) } else { - o.Websocket.RemoveSubscriptions(channels...) + err = o.Websocket.RemoveSubscriptions(channels...) } } else { if authenticatedChannelSubscription { - o.Websocket.AddSuccessfulSubscriptions(authChannels...) + err = o.Websocket.AddSuccessfulSubscriptions(authChannels...) } else { - o.Websocket.AddSuccessfulSubscriptions(channels...) + err = o.Websocket.AddSuccessfulSubscriptions(channels...) } } + if err != nil { + return err + } // Drop prior unsubs and chunked payload args on successful unsubscription if authenticatedChannelSubscription { authChannels = nil @@ -958,23 +964,19 @@ func (o *Okcoin) handleSubscriptions(operation string, subs []subscription.Subsc } } if len(subscriptionRequest.Arguments) > 0 { - err = o.Websocket.Conn.SendJSONMessage(subscriptionRequest) - if err != nil { + if err := o.Websocket.Conn.SendJSONMessage(subscriptionRequest); err != nil { return err } } if len(authRequest.Arguments) > 0 { - err = o.Websocket.AuthConn.SendJSONMessage(authRequest) - if err != nil { + if err := o.Websocket.AuthConn.SendJSONMessage(authRequest); err != nil { return err } } if operation == "unsubscribe" { - o.Websocket.RemoveSubscriptions(channels...) - } else { - o.Websocket.AddSuccessfulSubscriptions(channels...) + return o.Websocket.RemoveSubscriptions(channels...) } - return nil + return o.Websocket.AddSuccessfulSubscriptions(channels...) } // GetCandlesData represents a candlestick instances list. diff --git a/exchanges/okx/okx_websocket.go b/exchanges/okx/okx_websocket.go index 3b5a9d2ebe6..ede276a4c97 100644 --- a/exchanges/okx/okx_websocket.go +++ b/exchanges/okx/okx_websocket.go @@ -348,29 +348,31 @@ func (ok *Okx) wsReadData(ws stream.Connection) { } // Subscribe sends a websocket subscription request to several channels to receive data. -func (ok *Okx) Subscribe(channelsToSubscribe []subscription.Subscription) error { +func (ok *Okx) Subscribe(channelsToSubscribe subscription.List) error { return ok.handleSubscription(operationSubscribe, channelsToSubscribe) } // Unsubscribe sends a websocket unsubscription request to several channels to receive data. -func (ok *Okx) Unsubscribe(channelsToUnsubscribe []subscription.Subscription) error { +func (ok *Okx) Unsubscribe(channelsToUnsubscribe subscription.List) error { return ok.handleSubscription(operationUnsubscribe, channelsToUnsubscribe) } // handleSubscription sends a subscription and unsubscription information thought the websocket endpoint. // as of the okx, exchange this endpoint sends subscription and unsubscription messages but with a list of json objects. -func (ok *Okx) handleSubscription(operation string, subscriptions []subscription.Subscription) error { +func (ok *Okx) handleSubscription(operation string, subscriptions subscription.List) error { request := WSSubscriptionInformationList{Operation: operation} authRequests := WSSubscriptionInformationList{Operation: operation} ok.WsRequestSemaphore <- 1 defer func() { <-ok.WsRequestSemaphore }() - var channels []subscription.Subscription - var authChannels []subscription.Subscription - var err error - var format currency.PairFormat + var channels subscription.List + var authChannels subscription.List for i := 0; i < len(subscriptions); i++ { + s := subscriptions[i] + if len(s.Pairs) > 1 { + return subscription.ErrNotSinglePair + } arg := SubscriptionInfo{ - Channel: subscriptions[i].Channel, + Channel: s.Channel, } var instrumentID string var underlying string @@ -400,12 +402,12 @@ func (ok *Okx) handleSubscription(operation string, subscriptions []subscription } if arg.Channel == okxChannelGridPositions { - algoID, _ = subscriptions[i].Params["algoId"].(string) + algoID, _ = s.Params["algoId"].(string) } if arg.Channel == okcChannelGridSubOrders || arg.Channel == okxChannelGridPositions { - uid, _ = subscriptions[i].Params["uid"].(string) + uid, _ = s.Params["uid"].(string) } if strings.HasPrefix(arg.Channel, "candle") || @@ -416,26 +418,30 @@ func (ok *Okx) handleSubscription(operation string, subscriptions []subscription arg.Channel == okxChannelOrderBooksTBT || arg.Channel == okxChannelFundingRate || arg.Channel == okxChannelTrades { - if subscriptions[i].Params["instId"] != "" { - instrumentID, okay = subscriptions[i].Params["instId"].(string) + if s.Params["instId"] != "" { + instrumentID, okay = s.Params["instId"].(string) if !okay { instrumentID = "" } - } else if subscriptions[i].Params["instrumentID"] != "" { - instrumentID, okay = subscriptions[i].Params["instrumentID"].(string) + } else if s.Params["instrumentID"] != "" { + instrumentID, okay = s.Params["instrumentID"].(string) if !okay { instrumentID = "" } } if instrumentID == "" { - format, err = ok.GetPairFormat(subscriptions[i].Asset, false) + if len(s.Pairs) != 1 { + return subscription.ErrNotSinglePair + } + format, err := ok.GetPairFormat(s.Asset, false) if err != nil { return err } - if subscriptions[i].Pair.Base.String() == "" || subscriptions[i].Pair.Quote.String() == "" { + p := s.Pairs[0] + if p.Base.String() == "" || p.Quote.String() == "" { return errIncompleteCurrencyPair } - instrumentID = format.Format(subscriptions[i].Pair) + instrumentID = format.Format(p) } } if arg.Channel == okxChannelInstruments || @@ -447,7 +453,7 @@ func (ok *Okx) handleSubscription(operation string, subscriptions []subscription arg.Channel == okxChannelSpotGridOrder || arg.Channel == okxChannelGridOrdersContract || arg.Channel == okxChannelEstimatedPrice { - instrumentType = ok.GetInstrumentTypeFromAssetItem(subscriptions[i].Asset) + instrumentType = ok.GetInstrumentTypeFromAssetItem(s.Asset) } if arg.Channel == okxChannelPositions || @@ -455,7 +461,9 @@ func (ok *Okx) handleSubscription(operation string, subscriptions []subscription arg.Channel == okxChannelAlgoOrders || arg.Channel == okxChannelEstimatedPrice || arg.Channel == okxChannelOptSummary { - underlying, _ = ok.GetUnderlying(subscriptions[i].Pair, subscriptions[i].Asset) + if len(s.Pairs) == 1 { + underlying, _ = ok.GetUnderlying(s.Pairs[0], s.Asset) + } } arg.InstrumentID = instrumentID arg.Underlying = underlying @@ -464,10 +472,9 @@ func (ok *Okx) handleSubscription(operation string, subscriptions []subscription arg.AlgoID = algoID if authSubscription { - var authChunk []byte - authChannels = append(authChannels, subscriptions[i]) + authChannels = append(authChannels, s) authRequests.Arguments = append(authRequests.Arguments, arg) - authChunk, err = json.Marshal(authRequests) + authChunk, err := json.Marshal(authRequests) if err != nil { return err } @@ -479,18 +486,20 @@ func (ok *Okx) handleSubscription(operation string, subscriptions []subscription return err } if operation == operationUnsubscribe { - ok.Websocket.RemoveSubscriptions(channels...) + err = ok.Websocket.RemoveSubscriptions(channels...) } else { - ok.Websocket.AddSuccessfulSubscriptions(channels...) + err = ok.Websocket.AddSuccessfulSubscriptions(channels...) + } + if err != nil { + return err } - authChannels = []subscription.Subscription{} + authChannels = subscription.List{} authRequests.Arguments = []SubscriptionInfo{} } } else { - var chunk []byte - channels = append(channels, subscriptions[i]) + channels = append(channels, s) request.Arguments = append(request.Arguments, arg) - chunk, err = json.Marshal(request) + chunk, err := json.Marshal(request) if err != nil { return err } @@ -501,41 +510,38 @@ func (ok *Okx) handleSubscription(operation string, subscriptions []subscription return err } if operation == operationUnsubscribe { - ok.Websocket.RemoveSubscriptions(channels...) + err = ok.Websocket.RemoveSubscriptions(channels...) } else { - ok.Websocket.AddSuccessfulSubscriptions(channels...) + err = ok.Websocket.AddSuccessfulSubscriptions(channels...) + } + if err != nil { + return err } - channels = []subscription.Subscription{} + channels = subscription.List{} request.Arguments = []SubscriptionInfo{} continue } } } + if len(request.Arguments) > 0 { - err = ok.Websocket.Conn.SendJSONMessage(request) - if err != nil { + if err := ok.Websocket.Conn.SendJSONMessage(request); err != nil { return err } } if len(authRequests.Arguments) > 0 && ok.Websocket.CanUseAuthenticatedEndpoints() { - err = ok.Websocket.AuthConn.SendJSONMessage(authRequests) - if err != nil { + if err := ok.Websocket.AuthConn.SendJSONMessage(authRequests); err != nil { return err } } - if err != nil { - return err - } + channels = append(channels, authChannels...) if operation == operationUnsubscribe { - channels = append(channels, authChannels...) - ok.Websocket.RemoveSubscriptions(channels...) - } else { - channels = append(channels, authChannels...) - ok.Websocket.AddSuccessfulSubscriptions(channels...) + return ok.Websocket.RemoveSubscriptions(channels...) } - return nil + + return ok.Websocket.AddSuccessfulSubscriptions(channels...) } // WsHandleData will read websocket raw data and pass to appropriate handler @@ -833,11 +839,11 @@ func (ok *Okx) wsProcessOrderBooks(data []byte) error { } if err != nil { if errors.Is(err, errInvalidChecksum) { - err = ok.Subscribe([]subscription.Subscription{ + err = ok.Subscribe(subscription.List{ { Channel: response.Argument.Channel, Asset: assets[0], - Pair: pair, + Pairs: currency.Pairs{pair}, }, }) if err != nil { @@ -1294,8 +1300,8 @@ func (ok *Okx) wsProcessTickers(data []byte) error { } // GenerateDefaultSubscriptions returns a list of default subscription message. -func (ok *Okx) GenerateDefaultSubscriptions() ([]subscription.Subscription, error) { - var subscriptions []subscription.Subscription +func (ok *Okx) GenerateDefaultSubscriptions() (subscription.List, error) { + var subscriptions subscription.List assets := ok.GetAssetTypes(true) subs := make([]string, 0, len(defaultSubscribedChannels)+len(defaultAuthChannels)) subs = append(subs, defaultSubscribedChannels...) @@ -1306,7 +1312,7 @@ func (ok *Okx) GenerateDefaultSubscriptions() ([]subscription.Subscription, erro switch subs[c] { case okxChannelOrders: for x := range assets { - subscriptions = append(subscriptions, subscription.Subscription{ + subscriptions = append(subscriptions, &subscription.Subscription{ Channel: subs[c], Asset: assets[x], }) @@ -1318,15 +1324,15 @@ func (ok *Okx) GenerateDefaultSubscriptions() ([]subscription.Subscription, erro return nil, err } for p := range pairs { - subscriptions = append(subscriptions, subscription.Subscription{ + subscriptions = append(subscriptions, &subscription.Subscription{ Channel: subs[c], Asset: assets[x], - Pair: pairs[p], + Pairs: currency.Pairs{pairs[p]}, }) } } default: - subscriptions = append(subscriptions, subscription.Subscription{ + subscriptions = append(subscriptions, &subscription.Subscription{ Channel: subs[c], }) } @@ -1857,8 +1863,6 @@ func (ok *Okx) wsAuthChannelSubscription(operation, channel string, assetType as var instrumentID string var instrumentType string var ccy string - var err error - var format currency.PairFormat if params.InstrumentType { instrumentType = ok.GetInstrumentTypeFromAssetItem(assetType) if instrumentType != okxInstTypeMargin && @@ -1874,13 +1878,13 @@ func (ok *Okx) wsAuthChannelSubscription(operation, channel string, assetType as } } if params.InstrumentID { - format, err = ok.GetPairFormat(assetType, false) - if err != nil { - return err - } if !pair.IsPopulated() { return errIncompleteCurrencyPair } + format, err := ok.GetPairFormat(assetType, false) + if err != nil { + return err + } instrumentID = format.Format(pair) } if params.Currency { diff --git a/exchanges/poloniex/poloniex_types.go b/exchanges/poloniex/poloniex_types.go index 0d11149cbbe..a0bef20f3fc 100644 --- a/exchanges/poloniex/poloniex_types.go +++ b/exchanges/poloniex/poloniex_types.go @@ -351,10 +351,16 @@ type WebsocketTrollboxMessage struct { Reputation float64 } -// WsCommand defines the request params after a websocket connection has been -// established -type WsCommand struct { - Command string `json:"command"` +type wsOp string + +const ( + wsSubscribeOp wsOp = "subscribe" + wsUnsubscribeOp wsOp = "unsubscribe" +) + +// wsCommand defines the request params after a websocket connection has been established +type wsCommand struct { + Command wsOp `json:"command"` Channel interface{} `json:"channel"` APIKey string `json:"key,omitempty"` Payload string `json:"payload,omitempty"` @@ -467,9 +473,9 @@ type WsTradeNotificationResponse struct { Date time.Time } -// WsAuthorisationRequest Authenticated Ws Account data request -type WsAuthorisationRequest struct { - Command string `json:"command"` +// wsAuthorisationRequest Authenticated Ws Account data request +type wsAuthorisationRequest struct { + Command wsOp `json:"command"` Channel int64 `json:"channel"` Sign string `json:"sign"` Key string `json:"key"` diff --git a/exchanges/poloniex/poloniex_websocket.go b/exchanges/poloniex/poloniex_websocket.go index 0ab458487bd..a5407915519 100644 --- a/exchanges/poloniex/poloniex_websocket.go +++ b/exchanges/poloniex/poloniex_websocket.go @@ -541,28 +541,28 @@ func (p *Poloniex) WsProcessOrderbookUpdate(sequenceNumber float64, data []inter } // GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions() -func (p *Poloniex) GenerateDefaultSubscriptions() ([]subscription.Subscription, error) { +func (p *Poloniex) GenerateDefaultSubscriptions() (subscription.List, error) { enabledPairs, err := p.GetEnabledPairs(asset.Spot) if err != nil { return nil, err } - subscriptions := make([]subscription.Subscription, 0, len(enabledPairs)) - subscriptions = append(subscriptions, subscription.Subscription{ + subscriptions := make(subscription.List, 0, len(enabledPairs)) + subscriptions = append(subscriptions, &subscription.Subscription{ Channel: strconv.FormatInt(wsTickerDataID, 10), }) if p.IsWebsocketAuthenticationSupported() { - subscriptions = append(subscriptions, subscription.Subscription{ + subscriptions = append(subscriptions, &subscription.Subscription{ Channel: strconv.FormatInt(wsAccountNotificationID, 10), }) } for j := range enabledPairs { enabledPairs[j].Delimiter = currency.UnderscoreDelimiter - subscriptions = append(subscriptions, subscription.Subscription{ + subscriptions = append(subscriptions, &subscription.Subscription{ Channel: "orderbook", - Pair: enabledPairs[j], + Pairs: currency.Pairs{enabledPairs[j]}, Asset: asset.Spot, }) } @@ -570,54 +570,16 @@ func (p *Poloniex) GenerateDefaultSubscriptions() ([]subscription.Subscription, } // Subscribe sends a websocket message to receive data from the channel -func (p *Poloniex) Subscribe(sub []subscription.Subscription) error { - var creds *account.Credentials - if p.IsWebsocketAuthenticationSupported() { - var err error - creds, err = p.GetCredentials(context.TODO()) - if err != nil { - return err - } - } - var errs error -channels: - for i := range sub { - subscriptionRequest := WsCommand{ - Command: "subscribe", - } - switch { - case strings.EqualFold(strconv.FormatInt(wsAccountNotificationID, 10), - sub[i].Channel) && creds != nil: - err := p.wsSendAuthorisedCommand(creds.Secret, creds.Key, "subscribe") - if err != nil { - errs = common.AppendError(errs, err) - continue channels - } - p.Websocket.AddSuccessfulSubscriptions(sub[i]) - continue channels - case strings.EqualFold(strconv.FormatInt(wsTickerDataID, 10), - sub[i].Channel): - subscriptionRequest.Channel = wsTickerDataID - default: - subscriptionRequest.Channel = sub[i].Pair.String() - } - - err := p.Websocket.Conn.SendJSONMessage(subscriptionRequest) - if err != nil { - errs = common.AppendError(errs, err) - continue - } - - p.Websocket.AddSuccessfulSubscriptions(sub[i]) - } - if errs != nil { - return errs - } - return nil +func (p *Poloniex) Subscribe(subs subscription.List) error { + return p.manageSubs(subs, wsSubscribeOp) } // Unsubscribe sends a websocket message to stop receiving data from the channel -func (p *Poloniex) Unsubscribe(unsub []subscription.Subscription) error { +func (p *Poloniex) Unsubscribe(subs subscription.List) error { + return p.manageSubs(subs, wsUnsubscribeOp) +} + +func (p *Poloniex) manageSubs(subs subscription.List, op wsOp) error { var creds *account.Credentials if p.IsWebsocketAuthenticationSupported() { var err error @@ -626,42 +588,39 @@ func (p *Poloniex) Unsubscribe(unsub []subscription.Subscription) error { return err } } + var errs error -channels: - for i := range unsub { - unsubscriptionRequest := WsCommand{ - Command: "unsubscribe", + for _, s := range subs { + var err error + if creds != nil && strings.EqualFold(strconv.FormatInt(wsAccountNotificationID, 10), s.Channel) { + err = p.wsSendAuthorisedCommand(creds.Secret, creds.Key, op) + } else { + req := wsCommand{Command: op} + if strings.EqualFold(strconv.FormatInt(wsTickerDataID, 10), s.Channel) { + req.Channel = wsTickerDataID + } else { + if len(s.Pairs) != 1 { + return subscription.ErrNotSinglePair + } + req.Channel = s.Pairs[0].String() + } + err = p.Websocket.Conn.SendJSONMessage(req) } - switch { - case strings.EqualFold(strconv.FormatInt(wsAccountNotificationID, 10), - unsub[i].Channel) && creds != nil: - err := p.wsSendAuthorisedCommand(creds.Secret, creds.Key, "unsubscribe") - if err != nil { - errs = common.AppendError(errs, err) - continue channels + if err == nil { + if op == wsSubscribeOp { + err = p.Websocket.AddSuccessfulSubscriptions(s) + } else { + err = p.Websocket.RemoveSubscriptions(s) } - p.Websocket.RemoveSubscriptions(unsub[i]) - continue channels - case strings.EqualFold(strconv.FormatInt(wsTickerDataID, 10), - unsub[i].Channel): - unsubscriptionRequest.Channel = wsTickerDataID - default: - unsubscriptionRequest.Channel = unsub[i].Pair.String() } - err := p.Websocket.Conn.SendJSONMessage(unsubscriptionRequest) if err != nil { errs = common.AppendError(errs, err) - continue } - p.Websocket.RemoveSubscriptions(unsub[i]) } - if errs != nil { - return errs - } - return nil + return errs } -func (p *Poloniex) wsSendAuthorisedCommand(secret, key, command string) error { +func (p *Poloniex) wsSendAuthorisedCommand(secret, key string, op wsOp) error { nonce := fmt.Sprintf("nonce=%v", time.Now().UnixNano()) hmac, err := crypto.GetHMAC(crypto.HashSHA512, []byte(nonce), @@ -669,8 +628,8 @@ func (p *Poloniex) wsSendAuthorisedCommand(secret, key, command string) error { if err != nil { return err } - request := WsAuthorisationRequest{ - Command: command, + request := wsAuthorisationRequest{ + Command: op, Channel: 1000, Sign: crypto.HexEncodeToString(hmac), Key: key, diff --git a/exchanges/sharedtestvalues/customex.go b/exchanges/sharedtestvalues/customex.go index 7876ba58b20..e7735dd793d 100644 --- a/exchanges/sharedtestvalues/customex.go +++ b/exchanges/sharedtestvalues/customex.go @@ -262,7 +262,7 @@ func (c *CustomEx) SupportsREST() bool { } // GetSubscriptions is a mock method for CustomEx -func (c *CustomEx) GetSubscriptions() ([]subscription.Subscription, error) { +func (c *CustomEx) GetSubscriptions() (subscription.List, error) { return nil, nil } @@ -312,12 +312,12 @@ func (c *CustomEx) SupportsWebsocket() bool { } // SubscribeToWebsocketChannels is a mock method for CustomEx -func (c *CustomEx) SubscribeToWebsocketChannels(_ []subscription.Subscription) error { +func (c *CustomEx) SubscribeToWebsocketChannels(_ subscription.List) error { return nil } // UnsubscribeToWebsocketChannels is a mock method for CustomEx -func (c *CustomEx) UnsubscribeToWebsocketChannels(_ []subscription.Subscription) error { +func (c *CustomEx) UnsubscribeToWebsocketChannels(_ subscription.List) error { return nil } diff --git a/exchanges/sharedtestvalues/sharedtestvalues.go b/exchanges/sharedtestvalues/sharedtestvalues.go index 5e5a124abde..750fbae67dc 100644 --- a/exchanges/sharedtestvalues/sharedtestvalues.go +++ b/exchanges/sharedtestvalues/sharedtestvalues.go @@ -15,8 +15,6 @@ import ( exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/stream" - "github.com/thrasher-corp/gocryptotrader/exchanges/stream/buffer" - "github.com/thrasher-corp/gocryptotrader/exchanges/subscription" ) // This package is only to be referenced in test files @@ -54,16 +52,10 @@ func GetWebsocketStructChannelOverride() chan struct{} { // NewTestWebsocket returns a test websocket object func NewTestWebsocket() *stream.Websocket { - return &stream.Websocket{ - DataHandler: make(chan interface{}, WebsocketChannelOverrideCapacity), - ToRoutine: make(chan interface{}, 1000), - TrafficAlert: make(chan struct{}), - ReadMessageErrors: make(chan error), - Subscribe: make(chan []subscription.Subscription, 10), - Unsubscribe: make(chan []subscription.Subscription, 10), - Match: stream.NewMatch(), - Orderbook: buffer.Orderbook{}, - } + w := stream.NewWebsocket() + w.DataHandler = make(chan interface{}, WebsocketChannelOverrideCapacity) + w.ToRoutine = make(chan interface{}, 1000) + return w } // SkipTestIfCredentialsUnset is a test helper function checking if the diff --git a/exchanges/stream/websocket.go b/exchanges/stream/websocket.go index e7585e9449b..badb5d565a4 100644 --- a/exchanges/stream/websocket.go +++ b/exchanges/stream/websocket.go @@ -5,12 +5,14 @@ import ( "fmt" "net" "net/url" - "sync" + "slices" "time" "github.com/gorilla/websocket" "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/config" + "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream/buffer" "github.com/thrasher-corp/gocryptotrader/exchanges/subscription" "github.com/thrasher-corp/gocryptotrader/log" ) @@ -22,12 +24,9 @@ const ( // Public websocket errors var ( ErrWebsocketNotEnabled = errors.New("websocket not enabled") - ErrSubscriptionNotFound = errors.New("subscription not found") - ErrSubscribedAlready = errors.New("duplicate subscription") ErrSubscriptionFailure = errors.New("subscription failure") ErrSubscriptionNotSupported = errors.New("subscription channel not supported ") ErrUnsubscribeFailure = errors.New("unsubscribe failure") - ErrChannelInStateAlready = errors.New("channel already in state") ErrAlreadyDisabled = errors.New("websocket already disabled") ErrNotConnected = errors.New("websocket is not connected") ) @@ -57,9 +56,6 @@ var ( errClosedConnection = errors.New("use of closed network connection") errSubscriptionsExceedsLimit = errors.New("subscriptions exceeds limit") errInvalidMaxSubscriptions = errors.New("max subscriptions cannot be less than 0") - errNoSubscriptionsSupplied = errors.New("no subscriptions supplied") - errChannelAlreadySubscribed = errors.New("channel already subscribed") - errInvalidChannelState = errors.New("invalid Channel state") errSameProxyAddress = errors.New("cannot set proxy address to the same address") errNoConnectFunc = errors.New("websocket connect func not set") errAlreadyConnected = errors.New("websocket already connected") @@ -84,11 +80,13 @@ func NewWebsocket() *Websocket { return &Websocket{ DataHandler: make(chan interface{}, jobBuffer), ToRoutine: make(chan interface{}, jobBuffer), + ShutdownC: make(chan struct{}), TrafficAlert: make(chan struct{}, 1), ReadMessageErrors: make(chan error), - Subscribe: make(chan []subscription.Subscription), - Unsubscribe: make(chan []subscription.Subscription), Match: NewMatch(), + subscriptions: subscription.NewStore(), + features: &protocol.Features{}, + Orderbook: buffer.Orderbook{}, } } @@ -181,7 +179,6 @@ func (w *Websocket) Setup(s *WebsocketSetup) error { w.trafficTimeout = s.ExchangeConfig.WebsocketTrafficTimeout w.ShutdownC = make(chan struct{}) - w.Wg = new(sync.WaitGroup) w.SetCanUseAuthenticatedEndpoints(s.ExchangeConfig.API.AuthenticatedWebsocketSupport) if err := w.Orderbook.Setup(s.ExchangeConfig, &s.OrderbookBufferConfig, w.DataHandler); err != nil { @@ -195,7 +192,7 @@ func (w *Websocket) Setup(s *WebsocketSetup) error { return fmt.Errorf("%s %w", w.exchangeName, errInvalidMaxSubscriptions) } w.MaxSubscriptionsPerConnection = s.MaxWebsocketSubscriptionsPerConnection - w.setState(disconnected) + w.setState(disconnectedState) return nil } @@ -243,7 +240,7 @@ func (w *Websocket) SetupNewConnection(c ConnectionSetup) error { Traffic: w.TrafficAlert, readMessageErrors: w.ReadMessageErrors, ShutdownC: w.ShutdownC, - Wg: w.Wg, + Wg: &w.Wg, Match: w.Match, RateLimit: c.RateLimit, Reporter: c.ConnectionLevelReporter, @@ -277,20 +274,21 @@ func (w *Websocket) Connect() error { return fmt.Errorf("%v %w", w.exchangeName, errAlreadyConnected) } - w.subscriptionMutex.Lock() - w.subscriptions = subscriptionMap{} - w.subscriptionMutex.Unlock() + if w.subscriptions == nil { + return fmt.Errorf("%w: subscriptions", common.ErrNilPointer) + } + w.subscriptions.Clear() w.dataMonitor() w.trafficMonitor() - w.setState(connecting) + w.setState(connectingState) err := w.connector() if err != nil { - w.setState(disconnected) + w.setState(disconnectedState) return fmt.Errorf("%v Error connecting %w", w.exchangeName, err) } - w.setState(connected) + w.setState(connectedState) if !w.IsConnectionMonitorRunning() { err = w.connectionMonitor() @@ -303,17 +301,12 @@ func (w *Websocket) Connect() error { if err != nil { return fmt.Errorf("%s websocket: %w", w.exchangeName, common.AppendError(ErrSubscriptionFailure, err)) } - if len(subs) == 0 { - return nil - } - err = w.checkSubscriptions(subs) - if err != nil { - return fmt.Errorf("%s websocket: %w", w.exchangeName, common.AppendError(ErrSubscriptionFailure, err)) - } - err = w.Subscriber(subs) - if err != nil { - return fmt.Errorf("%s websocket: %w", w.exchangeName, common.AppendError(ErrSubscriptionFailure, err)) + if len(subs) != 0 { + if err := w.SubscribeToChannels(subs); err != nil { + return err + } } + return nil } @@ -469,11 +462,9 @@ func (w *Websocket) Shutdown() error { } // flush any subscriptions from last connection if needed - w.subscriptionMutex.Lock() - w.subscriptions = subscriptionMap{} - w.subscriptionMutex.Unlock() + w.subscriptions.Clear() - w.setState(disconnected) + w.setState(disconnectedState) close(w.ShutdownC) w.Wg.Wait() @@ -527,9 +518,7 @@ func (w *Websocket) FlushChannels() error { if len(newsubs) != 0 { // Purge subscription list as there will be conflicts - w.subscriptionMutex.Lock() - w.subscriptions = subscriptionMap{} - w.subscriptionMutex.Unlock() + w.subscriptions.Clear() return w.SubscribeToChannels(newsubs) } return nil @@ -606,17 +595,17 @@ func (w *Websocket) setState(s uint32) { // IsInitialised returns whether the websocket has been Setup() already func (w *Websocket) IsInitialised() bool { - return w.state.Load() != uninitialised + return w.state.Load() != uninitialisedState } // IsConnected returns whether the websocket is connected func (w *Websocket) IsConnected() bool { - return w.state.Load() == connected + return w.state.Load() == connectedState } // IsConnecting returns whether the websocket is connecting func (w *Websocket) IsConnecting() bool { - return w.state.Load() == connecting + return w.state.Load() == connectingState } func (w *Websocket) setEnabled(b bool) { @@ -786,163 +775,134 @@ func (w *Websocket) GetName() string { // GetChannelDifference finds the difference between the subscribed channels // and the new subscription list when pairs are disabled or enabled. -func (w *Websocket) GetChannelDifference(genSubs []subscription.Subscription) (sub, unsub []subscription.Subscription) { - w.subscriptionMutex.RLock() - unsubMap := make(map[any]subscription.Subscription, len(w.subscriptions)) - for k, c := range w.subscriptions { - unsubMap[k] = *c - } - w.subscriptionMutex.RUnlock() - - for i := range genSubs { - key := genSubs[i].EnsureKeyed() - if _, ok := unsubMap[key]; ok { - delete(unsubMap, key) // If it's in both then we remove it from the unsubscribe list - } else { - sub = append(sub, genSubs[i]) // If it's in genSubs but not existing subs we want to subscribe - } - } - - for x := range unsubMap { - unsub = append(unsub, unsubMap[x]) +func (w *Websocket) GetChannelDifference(newSubs subscription.List) (sub, unsub subscription.List) { + if w.subscriptions == nil { + w.subscriptions = subscription.NewStore() } - - return + return w.subscriptions.Diff(newSubs) } -// UnsubscribeChannels unsubscribes from a websocket channel -func (w *Websocket) UnsubscribeChannels(channels []subscription.Subscription) error { - if len(channels) == 0 { - return fmt.Errorf("%s websocket: %w", w.exchangeName, errNoSubscriptionsSupplied) +// UnsubscribeChannels unsubscribes from a list of websocket channel +func (w *Websocket) UnsubscribeChannels(channels subscription.List) error { + if w.subscriptions == nil || len(channels) == 0 { + return nil // No channels to unsubscribe from is not an error } - w.subscriptionMutex.RLock() - - for i := range channels { - key := channels[i].EnsureKeyed() - if _, ok := w.subscriptions[key]; !ok { - w.subscriptionMutex.RUnlock() - return fmt.Errorf("%s websocket: %w: %+v", w.exchangeName, ErrSubscriptionNotFound, channels[i]) + for _, s := range channels { + if w.subscriptions.Get(s) == nil { + return fmt.Errorf("%w: %s", subscription.ErrNotFound, s) } } - w.subscriptionMutex.RUnlock() return w.Unsubscriber(channels) } // ResubscribeToChannel resubscribes to channel -func (w *Websocket) ResubscribeToChannel(subscribedChannel *subscription.Subscription) error { - err := w.UnsubscribeChannels([]subscription.Subscription{*subscribedChannel}) - if err != nil { +// Sets state to Resubscribing, and exchanges which want to maintain a lock on it can respect this state and not RemoveSubscription +// Errors if subscription is already subscribing +func (w *Websocket) ResubscribeToChannel(s *subscription.Subscription) error { + l := subscription.List{s} + if err := s.SetState(subscription.ResubscribingState); err != nil { + return fmt.Errorf("%w: %s", err, s) + } + if err := w.UnsubscribeChannels(l); err != nil { return err } - return w.SubscribeToChannels([]subscription.Subscription{*subscribedChannel}) + return w.SubscribeToChannels(l) } -// SubscribeToChannels appends supplied channels to channelsToSubscribe -func (w *Websocket) SubscribeToChannels(channels []subscription.Subscription) error { - if err := w.checkSubscriptions(channels); err != nil { - return fmt.Errorf("%s websocket: %w", w.exchangeName, common.AppendError(ErrSubscriptionFailure, err)) - } - if err := w.Subscriber(channels); err != nil { - return fmt.Errorf("%s websocket: %w", w.exchangeName, common.AppendError(ErrSubscriptionFailure, err)) +// SubscribeToChannels subscribes to websocket channels using the exchange specific Subscriber method +// Errors are returned for duplicates or exceeding max Subscriptions +func (w *Websocket) SubscribeToChannels(subs subscription.List) error { + if slices.Contains(subs, nil) { + return fmt.Errorf("%w: List parameter contains an nil element", common.ErrNilPointer) } - return nil -} - -// AddSubscription adds a subscription to the subscription lists -// Unlike AddSubscriptions this method will error if the subscription already exists -func (w *Websocket) AddSubscription(c *subscription.Subscription) error { - w.subscriptionMutex.Lock() - defer w.subscriptionMutex.Unlock() - if w.subscriptions == nil { - w.subscriptions = subscriptionMap{} + if err := w.checkSubscriptions(subs); err != nil { + return err } - key := c.EnsureKeyed() - if _, ok := w.subscriptions[key]; ok { - return ErrSubscribedAlready + if err := w.Subscriber(subs); err != nil { + return fmt.Errorf("%w: %w", ErrSubscriptionFailure, err) } - - n := *c // Fresh copy; we don't want to use the pointer we were given and allow encapsulation/locks to be bypassed - w.subscriptions[key] = &n - return nil } -// SetSubscriptionState sets an existing subscription state -// returns an error if the subscription is not found, or the new state is already set -func (w *Websocket) SetSubscriptionState(c *subscription.Subscription, state subscription.State) error { - w.subscriptionMutex.Lock() - defer w.subscriptionMutex.Unlock() - if w.subscriptions == nil { - w.subscriptions = subscriptionMap{} - } - key := c.EnsureKeyed() - p, ok := w.subscriptions[key] - if !ok { - return ErrSubscriptionNotFound +// AddSubscriptions adds subscriptions to the subscription store +// Sets state to Subscribing unless the state is already set +func (w *Websocket) AddSubscriptions(subs ...*subscription.Subscription) error { + if w == nil { + return fmt.Errorf("%w: AddSubscriptions called on nil Websocket", common.ErrNilPointer) } - if state == p.State { - return ErrChannelInStateAlready + if w.subscriptions == nil { + w.subscriptions = subscription.NewStore() } - if state > subscription.UnsubscribingState { - return errInvalidChannelState + var errs error + for _, s := range subs { + if s.State() == subscription.InactiveState { + if err := s.SetState(subscription.SubscribingState); err != nil { + errs = common.AppendError(errs, fmt.Errorf("%w: %s", err, s)) + } + } + if err := w.subscriptions.Add(s); err != nil { + errs = common.AppendError(errs, err) + } } - p.State = state - return nil + return errs } -// AddSuccessfulSubscriptions adds subscriptions to the subscription lists that -// has been successfully subscribed -func (w *Websocket) AddSuccessfulSubscriptions(channels ...subscription.Subscription) { - w.subscriptionMutex.Lock() - defer w.subscriptionMutex.Unlock() +// AddSuccessfulSubscriptions marks subscriptions as subscribed and adds them to the subscription store +func (w *Websocket) AddSuccessfulSubscriptions(subs ...*subscription.Subscription) error { + if w == nil { + return fmt.Errorf("%w: AddSuccessfulSubscriptions called on nil Websocket", common.ErrNilPointer) + } if w.subscriptions == nil { - w.subscriptions = subscriptionMap{} + w.subscriptions = subscription.NewStore() } - for _, cN := range channels { //nolint:gocritic // See below comment - c := cN // cN is an iteration var; Not safe to make a pointer to - key := c.EnsureKeyed() - c.State = subscription.SubscribedState - w.subscriptions[key] = &c + var errs error + for _, s := range subs { + if err := s.SetState(subscription.SubscribedState); err != nil { + errs = common.AppendError(errs, fmt.Errorf("%w: %s", err, s)) + } + if err := w.subscriptions.Add(s); err != nil { + errs = common.AppendError(errs, err) + } } + return errs } -// RemoveSubscriptions removes subscriptions from the subscription list -func (w *Websocket) RemoveSubscriptions(channels ...subscription.Subscription) { - w.subscriptionMutex.Lock() - defer w.subscriptionMutex.Unlock() +// RemoveSubscriptions removes subscriptions from the subscription list and sets the status to Unsubscribed +func (w *Websocket) RemoveSubscriptions(subs ...*subscription.Subscription) error { + if w == nil { + return fmt.Errorf("%w: RemoveSubscriptions called on nil Websocket", common.ErrNilPointer) + } if w.subscriptions == nil { - w.subscriptions = subscriptionMap{} + return fmt.Errorf("%w: RemoveSubscriptions called on uninitialised Websocket", common.ErrNilPointer) } - for i := range channels { - key := channels[i].EnsureKeyed() - delete(w.subscriptions, key) + var errs error + for _, s := range subs { + if err := s.SetState(subscription.UnsubscribedState); err != nil { + errs = common.AppendError(errs, fmt.Errorf("%w: %s", err, s)) + } + if err := w.subscriptions.Remove(s); err != nil { + errs = common.AppendError(errs, err) + } } + return errs } -// GetSubscription returns a pointer to a copy of the subscription at the key provided +// GetSubscription returns a subscription at the key provided // returns nil if no subscription is at that key or the key is nil +// Keys can implement subscription.MatchableKey in order to provide custom matching logic func (w *Websocket) GetSubscription(key any) *subscription.Subscription { - if key == nil || w == nil || w.subscriptions == nil { + if w == nil || w.subscriptions == nil || key == nil { return nil } - w.subscriptionMutex.RLock() - defer w.subscriptionMutex.RUnlock() - if s, ok := w.subscriptions[key]; ok { - c := *s - return &c - } - return nil + return w.subscriptions.Get(key) } // GetSubscriptions returns a new slice of the subscriptions -func (w *Websocket) GetSubscriptions() []subscription.Subscription { - w.subscriptionMutex.RLock() - defer w.subscriptionMutex.RUnlock() - subs := make([]subscription.Subscription, 0, len(w.subscriptions)) - for _, c := range w.subscriptions { - subs = append(subs, *c) +func (w *Websocket) GetSubscriptions() subscription.List { + if w == nil || w.subscriptions == nil { + return nil } - return subs + return w.subscriptions.List() } // SetCanUseAuthenticatedEndpoints sets canUseAuthenticatedEndpoints val in a thread safe manner @@ -978,28 +938,25 @@ func checkWebsocketURL(s string) error { return nil } -// checkSubscriptions checks subscriptions against the max subscription limit -// and if the subscription already exists. -func (w *Websocket) checkSubscriptions(subs []subscription.Subscription) error { - if len(subs) == 0 { - return errNoSubscriptionsSupplied +// checkSubscriptions checks subscriptions against the max subscription limit and if the subscription already exists +// The subscription state is not considered when counting existing subscriptions +func (w *Websocket) checkSubscriptions(subs subscription.List) error { + if w.subscriptions == nil { + return fmt.Errorf("%w: Websocket.subscriptions", common.ErrNilPointer) } - w.subscriptionMutex.RLock() - defer w.subscriptionMutex.RUnlock() - - if w.MaxSubscriptionsPerConnection > 0 && len(w.subscriptions)+len(subs) > w.MaxSubscriptionsPerConnection { + existing := w.subscriptions.Len() + if w.MaxSubscriptionsPerConnection > 0 && existing+len(subs) > w.MaxSubscriptionsPerConnection { return fmt.Errorf("%w: current subscriptions: %v, incoming subscriptions: %v, max subscriptions per connection: %v - please reduce enabled pairs", errSubscriptionsExceedsLimit, - len(w.subscriptions), + existing, len(subs), w.MaxSubscriptionsPerConnection) } - for i := range subs { - key := subs[i].EnsureKeyed() - if _, ok := w.subscriptions[key]; ok { - return fmt.Errorf("%w for %+v", errChannelAlreadySubscribed, subs[i]) + for _, s := range subs { + if found := w.subscriptions.Get(s); found != nil { + return fmt.Errorf("%w: %s", subscription.ErrDuplicate, s) } } diff --git a/exchanges/stream/websocket_connection.go b/exchanges/stream/websocket_connection.go index 4d7681f8d13..6a00d01ab74 100644 --- a/exchanges/stream/websocket_connection.go +++ b/exchanges/stream/websocket_connection.go @@ -90,25 +90,22 @@ func (w *WebsocketConnection) Dial(dialer *websocket.Dialer, headers http.Header // SendJSONMessage sends a JSON encoded message over the connection func (w *WebsocketConnection) SendJSONMessage(data interface{}) error { if !w.IsConnected() { - return fmt.Errorf("%s websocket connection: cannot send message to a disconnected websocket", - w.ExchangeName) + return fmt.Errorf("%s websocket connection: cannot send message to a disconnected websocket", w.ExchangeName) } w.writeControl.Lock() defer w.writeControl.Unlock() if w.Verbose { - log.Debugf(log.WebsocketMgr, - "%s websocket connection: sending message to websocket %+v\n", - w.ExchangeName, - data) + if msg, err := json.Marshal(data); err == nil { // WriteJSON will error for us anyway + log.Debugf(log.WebsocketMgr, "%s websocket connection: sending message: %s\n", w.ExchangeName, msg) + } } if w.RateLimit > 0 { time.Sleep(time.Duration(w.RateLimit) * time.Millisecond) if !w.IsConnected() { - return fmt.Errorf("%v websocket connection: cannot send message to a disconnected websocket", - w.ExchangeName) + return fmt.Errorf("%v websocket connection: cannot send message to a disconnected websocket", w.ExchangeName) } } return w.Connection.WriteJSON(data) @@ -117,29 +114,23 @@ func (w *WebsocketConnection) SendJSONMessage(data interface{}) error { // SendRawMessage sends a message over the connection without JSON encoding it func (w *WebsocketConnection) SendRawMessage(messageType int, message []byte) error { if !w.IsConnected() { - return fmt.Errorf("%v websocket connection: cannot send message to a disconnected websocket", - w.ExchangeName) + return fmt.Errorf("%v websocket connection: cannot send message to a disconnected websocket", w.ExchangeName) } w.writeControl.Lock() defer w.writeControl.Unlock() if w.Verbose { - log.Debugf(log.WebsocketMgr, - "%v websocket connection: sending message [%s]\n", - w.ExchangeName, - message) + log.Debugf(log.WebsocketMgr, "%v websocket connection: sending message [%s]\n", w.ExchangeName, message) } if w.RateLimit > 0 { time.Sleep(time.Duration(w.RateLimit) * time.Millisecond) if !w.IsConnected() { - return fmt.Errorf("%v websocket connection: cannot send message to a disconnected websocket", - w.ExchangeName) + return fmt.Errorf("%v websocket connection: cannot send message to a disconnected websocket", w.ExchangeName) } } if !w.IsConnected() { - return fmt.Errorf("%v websocket connection: cannot send message to a disconnected websocket", - w.ExchangeName) + return fmt.Errorf("%v websocket connection: cannot send message to a disconnected websocket", w.ExchangeName) } return w.Connection.WriteMessage(messageType, message) } diff --git a/exchanges/stream/websocket_test.go b/exchanges/stream/websocket_test.go index 5e4891f8030..2d44de95097 100644 --- a/exchanges/stream/websocket_test.go +++ b/exchanges/stream/websocket_test.go @@ -10,7 +10,6 @@ import ( "net" "net/http" "os" - "sort" "strconv" "strings" "sync" @@ -20,6 +19,7 @@ import ( "github.com/gorilla/websocket" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" @@ -79,10 +79,10 @@ var defaultSetup = &WebsocketSetup{ DefaultURL: "testDefaultURL", RunningURL: "wss://testRunningURL", Connector: func() error { return nil }, - Subscriber: func([]subscription.Subscription) error { return nil }, - Unsubscriber: func([]subscription.Subscription) error { return nil }, - GenerateSubscriptions: func() ([]subscription.Subscription, error) { - return []subscription.Subscription{ + Subscriber: func(subscription.List) error { return nil }, + Unsubscriber: func(subscription.List) error { return nil }, + GenerateSubscriptions: func() (subscription.List, error) { + return subscription.List{ {Channel: "TestSub"}, {Channel: "TestSub2", Key: "purple"}, {Channel: "TestSub3", Key: testSubKey{"mauve"}}, @@ -147,16 +147,16 @@ func TestSetup(t *testing.T) { err = w.Setup(websocketSetup) assert.ErrorIs(t, err, errWebsocketSubscriberUnset, "Setup should error correctly") - websocketSetup.Subscriber = func([]subscription.Subscription) error { return nil } + websocketSetup.Subscriber = func(subscription.List) error { return nil } websocketSetup.Features.Unsubscribe = true err = w.Setup(websocketSetup) assert.ErrorIs(t, err, errWebsocketUnsubscriberUnset, "Setup should error correctly") - websocketSetup.Unsubscriber = func([]subscription.Subscription) error { return nil } + websocketSetup.Unsubscriber = func(subscription.List) error { return nil } err = w.Setup(websocketSetup) assert.ErrorIs(t, err, errWebsocketSubscriptionsGeneratorUnset, "Setup should error correctly") - websocketSetup.GenerateSubscriptions = func() ([]subscription.Subscription, error) { return nil, nil } + websocketSetup.GenerateSubscriptions = func() (subscription.List, error) { return nil, nil } err = w.Setup(websocketSetup) assert.ErrorIs(t, err, errDefaultURLIsEmpty, "Setup should error correctly") @@ -193,14 +193,13 @@ func TestTrafficMonitorTrafficAlerts(t *testing.T) { signal := struct{}{} patience := 10 * time.Millisecond ws.trafficTimeout = 200 * time.Millisecond - ws.ShutdownC = make(chan struct{}) - ws.state.Store(connected) + ws.state.Store(connectedState) thenish := time.Now() ws.trafficMonitor() assert.True(t, ws.IsTrafficMonitorRunning(), "traffic monitor should be running") - require.Equal(t, connected, ws.state.Load(), "websocket must be connected") + require.Equal(t, connectedState, ws.state.Load(), "websocket must be connected") for i := 0; i < 6; i++ { // Timeout will happen at 200ms so we want 6 * 50ms checks to pass select { @@ -226,7 +225,7 @@ func TestTrafficMonitorTrafficAlerts(t *testing.T) { } require.EventuallyWithT(t, func(c *assert.CollectT) { - assert.Equal(c, disconnected, ws.state.Load(), "websocket must be disconnected") + assert.Equal(c, disconnectedState, ws.state.Load(), "websocket must be disconnected") assert.False(c, ws.IsTrafficMonitorRunning(), "trafficMonitor should be shut down") }, 2*ws.trafficTimeout, patience, "trafficTimeout should trigger a shutdown once we stop feeding trafficAlerts") } @@ -238,17 +237,16 @@ func TestTrafficMonitorConnecting(t *testing.T) { err := ws.Setup(defaultSetup) require.NoError(t, err, "Setup must not error") - ws.ShutdownC = make(chan struct{}) - ws.state.Store(connecting) + ws.state.Store(connectingState) ws.trafficTimeout = 50 * time.Millisecond ws.trafficMonitor() require.True(t, ws.IsTrafficMonitorRunning(), "traffic monitor should be running") - require.Equal(t, connecting, ws.state.Load(), "websocket must be connecting") + require.Equal(t, connectingState, ws.state.Load(), "websocket must be connecting") <-time.After(4 * ws.trafficTimeout) - require.Equal(t, connecting, ws.state.Load(), "websocket must still be connecting after several checks") - ws.state.Store(connected) + require.Equal(t, connectingState, ws.state.Load(), "websocket must still be connecting after several checks") + ws.state.Store(connectedState) require.EventuallyWithT(t, func(c *assert.CollectT) { - assert.Equal(c, disconnected, ws.state.Load(), "websocket must be disconnected") + assert.Equal(c, disconnectedState, ws.state.Load(), "websocket must be disconnected") assert.False(c, ws.IsTrafficMonitorRunning(), "trafficMonitor should be shut down") }, 4*ws.trafficTimeout, 10*time.Millisecond, "trafficTimeout should trigger a shutdown after connecting status changes") } @@ -260,8 +258,7 @@ func TestTrafficMonitorShutdown(t *testing.T) { err := ws.Setup(defaultSetup) require.NoError(t, err, "Setup must not error") - ws.ShutdownC = make(chan struct{}) - ws.state.Store(connected) + ws.state.Store(connectedState) ws.trafficTimeout = time.Minute ws.trafficMonitor() assert.True(t, ws.IsTrafficMonitorRunning(), "traffic monitor should be running") @@ -307,12 +304,17 @@ func TestConnectionMessageErrors(t *testing.T) { assert.ErrorIs(t, err, ErrWebsocketNotEnabled, "Connect should error correctly") wsWrong.setEnabled(true) - wsWrong.setState(connecting) - wsWrong.Wg = &sync.WaitGroup{} + wsWrong.setState(connectingState) err = wsWrong.Connect() assert.ErrorIs(t, err, errAlreadyReconnecting, "Connect should error correctly") - wsWrong.setState(disconnected) + wsWrong.setState(disconnectedState) + err = wsWrong.Connect() + assert.ErrorIs(t, err, common.ErrNilPointer, "Connect should get a nil pointer error") + assert.ErrorContains(t, err, "subscriptions", "Connect should get a nil pointer error about subscriptions") + + wsWrong.subscriptions = subscription.NewStore() + wsWrong.setState(disconnectedState) wsWrong.connector = func() error { return errDastardlyReason } err = wsWrong.Connect() assert.ErrorIs(t, err, errDastardlyReason, "Connect should error correctly") @@ -321,7 +323,7 @@ func TestConnectionMessageErrors(t *testing.T) { err = ws.Setup(defaultSetup) require.NoError(t, err, "Setup must not error") ws.trafficTimeout = time.Minute - ws.connector = func() error { return nil } + ws.connector = connect err = ws.Connect() require.NoError(t, err, "Connect must not error") @@ -381,7 +383,7 @@ func TestWebsocket(t *testing.T) { err = ws.SetProxyAddress("https://192.168.0.1:1337") assert.NoError(t, err, "SetProxyAddress should not error when not yet connected") - ws.setState(connected) + ws.setState(connectedState) err = ws.SetProxyAddress("https://192.168.0.1:1336") assert.ErrorIs(t, err, errDastardlyReason, "SetProxyAddress should call Connect and error from there") @@ -404,14 +406,14 @@ func TestWebsocket(t *testing.T) { assert.Equal(t, "wss://testRunningURL", ws.GetWebsocketURL(), "GetWebsocketURL should return correctly") assert.Equal(t, time.Second*5, ws.trafficTimeout, "trafficTimeout should default correctly") - ws.setState(connected) + ws.setState(connectedState) ws.AuthConn = &dodgyConnection{} err = ws.Shutdown() assert.ErrorIs(t, err, errDastardlyReason, "Shutdown should error correctly with a dodgy authConn") assert.ErrorIs(t, err, errCannotShutdown, "Shutdown should error correctly with a dodgy authConn") ws.AuthConn = &WebsocketConnection{} - ws.setState(disconnected) + ws.setState(disconnectedState) err = ws.Connect() assert.NoError(t, err, "Connect should not error") @@ -446,34 +448,39 @@ func TestWebsocket(t *testing.T) { ws.Wg.Wait() } +func currySimpleSub(w *Websocket) func(subscription.List) error { + return func(subs subscription.List) error { + return w.AddSuccessfulSubscriptions(subs...) + } +} + +func currySimpleUnsub(w *Websocket) func(subscription.List) error { + return func(unsubs subscription.List) error { + return w.RemoveSubscriptions(unsubs...) + } +} + // TestSubscribe logic test func TestSubscribeUnsubscribe(t *testing.T) { t.Parallel() ws := NewWebsocket() assert.NoError(t, ws.Setup(defaultSetup), "WS Setup should not error") - fnSub := func(subs []subscription.Subscription) error { - ws.AddSuccessfulSubscriptions(subs...) - return nil - } - fnUnsub := func(unsubs []subscription.Subscription) error { - ws.RemoveSubscriptions(unsubs...) - return nil - } - ws.Subscriber = fnSub - ws.Unsubscriber = fnUnsub + ws.Subscriber = currySimpleSub(ws) + ws.Unsubscriber = currySimpleUnsub(ws) subs, err := ws.GenerateSubs() - assert.NoError(t, err, "Generating test subscriptions should not error") - assert.ErrorIs(t, ws.UnsubscribeChannels(nil), errNoSubscriptionsSupplied, "Unsubscribing from nil should error") - assert.ErrorIs(t, ws.UnsubscribeChannels(subs), ErrSubscriptionNotFound, "Unsubscribing should error when not subscribed") + require.NoError(t, err, "Generating test subscriptions should not error") + assert.NoError(t, new(Websocket).UnsubscribeChannels(subs), "Should not error when w.subscriptions is nil") + assert.NoError(t, ws.UnsubscribeChannels(nil), "Unsubscribing from nil should not error") + assert.ErrorIs(t, ws.UnsubscribeChannels(subs), subscription.ErrNotFound, "Unsubscribing should error when not subscribed") assert.Nil(t, ws.GetSubscription(42), "GetSubscription on empty internal map should return") assert.NoError(t, ws.SubscribeToChannels(subs), "Basic Subscribing should not error") assert.Len(t, ws.GetSubscriptions(), 4, "Should have 4 subscriptions") - byDefKey := ws.GetSubscription(subscription.DefaultKey{Channel: "TestSub"}) - if assert.NotNil(t, byDefKey, "GetSubscription by default key should find a channel") { - assert.Equal(t, "TestSub", byDefKey.Channel, "GetSubscription by default key should return a pointer a copy of the right channel") - assert.NotSame(t, byDefKey, ws.subscriptions["TestSub"], "GetSubscription returns a fresh pointer") + bySub := ws.GetSubscription(subscription.Subscription{Channel: "TestSub"}) + if assert.NotNil(t, bySub, "GetSubscription by subscription should find a channel") { + assert.Equal(t, "TestSub", bySub.Channel, "GetSubscription by default key should return a pointer a copy of the right channel") + assert.Same(t, bySub, subs[0], "GetSubscription returns the same pointer") } if assert.NotNil(t, ws.GetSubscription("purple"), "GetSubscription by string key should find a channel") { assert.Equal(t, "TestSub2", ws.GetSubscription("purple").Channel, "GetSubscription by string key should return a pointer a copy of the right channel") @@ -486,9 +493,15 @@ func TestSubscribeUnsubscribe(t *testing.T) { } assert.Nil(t, ws.GetSubscription(nil), "GetSubscription by nil should return nil") assert.Nil(t, ws.GetSubscription(45), "GetSubscription by invalid key should return nil") - assert.ErrorIs(t, ws.SubscribeToChannels(subs), errChannelAlreadySubscribed, "Subscribe should error when already subscribed") - assert.ErrorIs(t, ws.SubscribeToChannels(nil), errNoSubscriptionsSupplied, "Subscribe to nil should error") + assert.ErrorIs(t, ws.SubscribeToChannels(subs), subscription.ErrDuplicate, "Subscribe should error when already subscribed") + assert.NoError(t, ws.SubscribeToChannels(nil), "Subscribe to an nil List should not error") assert.NoError(t, ws.UnsubscribeChannels(subs), "Unsubscribing should not error") + + ws.Subscriber = func(subscription.List) error { return errDastardlyReason } + assert.ErrorIs(t, ws.SubscribeToChannels(subs), errDastardlyReason, "Should error correctly when error returned from Subscriber") + + err = ws.SubscribeToChannels(subscription.List{nil}) + assert.ErrorIs(t, err, common.ErrNilPointer, "Should error correctly when list contains a nil subscription") } // TestResubscribe tests Resubscribing to existing subscriptions @@ -504,61 +517,56 @@ func TestResubscribe(t *testing.T) { err = ws.Setup(defaultSetup) assert.NoError(t, err, "WS Setup should not error") - fnSub := func(subs []subscription.Subscription) error { - ws.AddSuccessfulSubscriptions(subs...) - return nil - } - fnUnsub := func(unsubs []subscription.Subscription) error { - ws.RemoveSubscriptions(unsubs...) - return nil - } - ws.Subscriber = fnSub - ws.Unsubscriber = fnUnsub + ws.Subscriber = currySimpleSub(ws) + ws.Unsubscriber = currySimpleUnsub(ws) - channel := []subscription.Subscription{{Channel: "resubTest"}} + channel := subscription.List{{Channel: "resubTest"}} - assert.ErrorIs(t, ws.ResubscribeToChannel(&channel[0]), ErrSubscriptionNotFound, "Resubscribe should error when channel isn't subscribed yet") + assert.ErrorIs(t, ws.ResubscribeToChannel(channel[0]), subscription.ErrNotFound, "Resubscribe should error when channel isn't subscribed yet") assert.NoError(t, ws.SubscribeToChannels(channel), "Subscribe should not error") - assert.NoError(t, ws.ResubscribeToChannel(&channel[0]), "Resubscribe should not error now the channel is subscribed") + assert.NoError(t, ws.ResubscribeToChannel(channel[0]), "Resubscribe should not error now the channel is subscribed") } -// TestSubscriptionState tests Subscription state changes -func TestSubscriptionState(t *testing.T) { +// TestSubscriptions tests adding, getting and removing subscriptions +func TestSubscriptions(t *testing.T) { t.Parallel() - ws := NewWebsocket() - - c := &subscription.Subscription{Key: 42, Channel: "Gophers", State: subscription.SubscribingState} - assert.ErrorIs(t, ws.SetSubscriptionState(c, subscription.UnsubscribingState), ErrSubscriptionNotFound, "Setting an imaginary sub should error") - - assert.NoError(t, ws.AddSubscription(c), "Adding first subscription should not error") - found := ws.GetSubscription(42) - assert.NotNil(t, found, "Should find the subscription") - assert.Equal(t, subscription.SubscribingState, found.State, "Subscription should be Subscribing") - assert.ErrorIs(t, ws.AddSubscription(c), ErrSubscribedAlready, "Adding an already existing sub should error") - assert.ErrorIs(t, ws.SetSubscriptionState(c, subscription.SubscribingState), ErrChannelInStateAlready, "Setting Same state should error") - assert.ErrorIs(t, ws.SetSubscriptionState(c, subscription.UnsubscribingState+1), errInvalidChannelState, "Setting an invalid state should error") - - ws.AddSuccessfulSubscriptions(*c) - found = ws.GetSubscription(42) - assert.NotNil(t, found, "Should find the subscription") - assert.Equal(t, subscription.SubscribedState, found.State, "Subscription should be subscribed state") - - assert.NoError(t, ws.SetSubscriptionState(c, subscription.UnsubscribingState), "Setting Unsub state should not error") - found = ws.GetSubscription(42) - assert.Equal(t, subscription.UnsubscribingState, found.State, "Subscription should be unsubscribing state") + w := new(Websocket) // Do not use NewWebsocket; We want to exercise w.subs == nil + assert.ErrorIs(t, (*Websocket)(nil).AddSubscriptions(nil), common.ErrNilPointer, "Should error correctly when nil websocket") + s := &subscription.Subscription{Key: 42, Channel: subscription.TickerChannel} + require.NoError(t, w.AddSubscriptions(s), "Adding first subscription should not error") + assert.Same(t, s, w.GetSubscription(42), "Get Subscription should retrieve the same subscription") + assert.ErrorIs(t, w.AddSubscriptions(s), subscription.ErrDuplicate, "Adding same subscription should return error") + assert.Equal(t, subscription.SubscribingState, s.State(), "Should set state to Subscribing") + + err := w.RemoveSubscriptions(s) + require.NoError(t, err, "RemoveSubscriptions must not error") + assert.Nil(t, w.GetSubscription(42), "Remove should have removed the sub") + assert.Equal(t, subscription.UnsubscribedState, s.State(), "Should set state to Unsubscribed") + + require.NoError(t, s.SetState(subscription.ResubscribingState), "SetState must not error") + require.NoError(t, w.AddSubscriptions(s), "Adding first subscription should not error") + assert.Equal(t, subscription.ResubscribingState, s.State(), "Should not change resubscribing state") } -// TestRemoveSubscriptions tests removing a subscription -func TestRemoveSubscriptions(t *testing.T) { +// TestSuccessfulSubscriptions tests adding, getting and removing subscriptions +func TestSuccessfulSubscriptions(t *testing.T) { t.Parallel() - ws := NewWebsocket() - - c := &subscription.Subscription{Key: 42, Channel: "Unite!"} - assert.NoError(t, ws.AddSubscription(c), "Adding first subscription should not error") - assert.NotNil(t, ws.GetSubscription(42), "Added subscription should be findable") - - ws.RemoveSubscriptions(*c) - assert.Nil(t, ws.GetSubscription(42), "Remove should have removed the sub") + w := new(Websocket) // Do not use NewWebsocket; We want to exercise w.subs == nil + assert.ErrorIs(t, (*Websocket)(nil).AddSuccessfulSubscriptions(nil), common.ErrNilPointer, "Should error correctly when nil websocket") + c := &subscription.Subscription{Key: 42, Channel: subscription.TickerChannel} + require.NoError(t, w.AddSuccessfulSubscriptions(c), "Adding first subscription should not error") + assert.Same(t, c, w.GetSubscription(42), "Get Subscription should retrieve the same subscription") + assert.ErrorIs(t, w.AddSuccessfulSubscriptions(c), subscription.ErrInStateAlready, "Adding subscription in same state should return error") + require.NoError(t, c.SetState(subscription.SubscribingState), "SetState must not error") + assert.ErrorIs(t, w.AddSuccessfulSubscriptions(c), subscription.ErrDuplicate, "Adding same subscription should return error") + + err := w.RemoveSubscriptions(c) + require.NoError(t, err, "RemoveSubscriptions must not error") + assert.Nil(t, w.GetSubscription(42), "Remove should have removed the sub") + assert.ErrorIs(t, w.RemoveSubscriptions(c), subscription.ErrNotFound, "Should error correctly when not found") + assert.ErrorIs(t, (*Websocket)(nil).RemoveSubscriptions(nil), common.ErrNilPointer, "Should error correctly when nil websocket") + w.subscriptions = nil + assert.ErrorIs(t, w.RemoveSubscriptions(c), common.ErrNilPointer, "Should error correctly when nil websocket") } // TestConnectionMonitorNoConnection logic test @@ -566,10 +574,7 @@ func TestConnectionMonitorNoConnection(t *testing.T) { t.Parallel() ws := NewWebsocket() ws.connectionMonitorDelay = 500 - ws.DataHandler = make(chan interface{}, 1) - ws.ShutdownC = make(chan struct{}, 1) ws.exchangeName = "hello" - ws.Wg = &sync.WaitGroup{} ws.setEnabled(true) err := ws.connectionMonitor() require.NoError(t, err, "connectionMonitor must not error") @@ -582,32 +587,27 @@ func TestConnectionMonitorNoConnection(t *testing.T) { func TestGetSubscription(t *testing.T) { t.Parallel() assert.Nil(t, (*Websocket).GetSubscription(nil, "imaginary"), "GetSubscription on a nil Websocket should return nil") - assert.Nil(t, (&Websocket{}).GetSubscription("empty"), "GetSubscription on a Websocket with no sub map should return nil") - w := Websocket{ - subscriptions: subscriptionMap{ - 42: { - Channel: "hello3", - }, - }, - } - assert.Nil(t, w.GetSubscription(43), "GetSubscription with an invalid key should return nil") - c := w.GetSubscription(42) - if assert.NotNil(t, c, "GetSubscription with an valid key should return a channel") { - assert.Equal(t, "hello3", c.Channel, "GetSubscription should return the correct channel details") - } + assert.Nil(t, (&Websocket{}).GetSubscription("empty"), "GetSubscription on a Websocket with no sub store should return nil") + w := NewWebsocket() + assert.Nil(t, w.GetSubscription(nil), "GetSubscription with a nil key should return nil") + s := &subscription.Subscription{Key: 42, Channel: "hello3"} + require.NoError(t, w.AddSubscriptions(s), "AddSubscriptions must not error") + assert.Same(t, s, w.GetSubscription(42), "GetSubscription should delegate to the store") } // TestGetSubscriptions logic test func TestGetSubscriptions(t *testing.T) { t.Parallel() - w := Websocket{ - subscriptions: subscriptionMap{ - 42: { - Channel: "hello3", - }, - }, + assert.Nil(t, (*Websocket).GetSubscriptions(nil), "GetSubscription on a nil Websocket should return nil") + assert.Nil(t, (&Websocket{}).GetSubscriptions(), "GetSubscription on a Websocket with no sub store should return nil") + w := NewWebsocket() + s := subscription.List{ + {Key: 42, Channel: "hello3"}, + {Key: 45, Channel: "hello4"}, } - assert.Equal(t, "hello3", w.GetSubscriptions()[0].Channel, "GetSubscriptions should return the correct channel details") + err := w.AddSubscriptions(s...) + require.NoError(t, err, "AddSubscriptions must not error") + assert.ElementsMatch(t, s, w.GetSubscriptions(), "GetSubscriptions should return the correct channel details") } // TestSetCanUseAuthenticatedEndpoints logic test @@ -883,7 +883,7 @@ func TestCanUseAuthenticatedWebsocketForWrapper(t *testing.T) { ws := &Websocket{} assert.False(t, ws.CanUseAuthenticatedWebsocketForWrapper(), "CanUseAuthenticatedWebsocketForWrapper should return false") - ws.setState(connected) + ws.setState(connectedState) require.True(t, ws.IsConnected(), "IsConnected must return true") assert.False(t, ws.CanUseAuthenticatedWebsocketForWrapper(), "CanUseAuthenticatedWebsocketForWrapper should return false") @@ -939,77 +939,42 @@ func TestCheckWebsocketURL(t *testing.T) { assert.NoError(t, err, "checkWebsocketURL should not error") } +// TestGetChannelDifference exercises GetChannelDifference +// See subscription.TestStoreDiff for further testing func TestGetChannelDifference(t *testing.T) { t.Parallel() - web := Websocket{} - - newChans := []subscription.Subscription{ - { - Channel: "Test1", - }, - { - Channel: "Test2", - }, - { - Channel: "Test3", - }, - } - subs, unsubs := web.GetChannelDifference(newChans) - assert.Len(t, subs, 3, "Should get the correct number of subs") - assert.Empty(t, unsubs, "Should get the correct number of unsubs") - - web.AddSuccessfulSubscriptions(subs...) - - flushedSubs := []subscription.Subscription{ - { - Channel: "Test2", - }, - } - - subs, unsubs = web.GetChannelDifference(flushedSubs) - assert.Empty(t, subs, "Should get the correct number of subs") - assert.Len(t, unsubs, 2, "Should get the correct number of unsubs") - flushedSubs = []subscription.Subscription{ - { - Channel: "Test2", - }, - { - Channel: "Test4", - }, - } - - subs, unsubs = web.GetChannelDifference(flushedSubs) - if assert.Len(t, subs, 1, "Should get the correct number of subs") { - assert.Equal(t, "Test4", subs[0].Channel, "Should subscribe to the right channel") - } - if assert.Len(t, unsubs, 2, "Should get the correct number of unsubs") { - sort.Slice(unsubs, func(i, j int) bool { return unsubs[i].Channel <= unsubs[j].Channel }) - assert.Equal(t, "Test1", unsubs[0].Channel, "Should unsubscribe from the right channels") - assert.Equal(t, "Test3", unsubs[1].Channel, "Should unsubscribe from the right channels") - } + w := &Websocket{} + assert.NotPanics(t, func() { w.GetChannelDifference(subscription.List{}) }, "Should not panic when called without a store") + subs, unsubs := w.GetChannelDifference(subscription.List{{Channel: subscription.CandlesChannel}}) + require.Equal(t, 1, len(subs), "Should get the correct number of subs") + require.Empty(t, unsubs, "Should get no unsubs") + require.NoError(t, w.AddSubscriptions(subs...), "AddSubscriptions must not error") + subs, unsubs = w.GetChannelDifference(subscription.List{{Channel: subscription.TickerChannel}}) + require.Equal(t, 1, len(subs), "Should get the correct number of subs") + assert.Equal(t, 1, len(unsubs), "Should get the correct number of unsubs") } // GenSubs defines a theoretical exchange with pair management type GenSubs struct { EnabledPairs currency.Pairs - subscribos []subscription.Subscription - unsubscribos []subscription.Subscription + subscribos subscription.List + unsubscribos subscription.List } // generateSubs default subs created from the enabled pairs list -func (g *GenSubs) generateSubs() ([]subscription.Subscription, error) { - superduperchannelsubs := make([]subscription.Subscription, len(g.EnabledPairs)) +func (g *GenSubs) generateSubs() (subscription.List, error) { + superduperchannelsubs := make(subscription.List, len(g.EnabledPairs)) for i := range g.EnabledPairs { - superduperchannelsubs[i] = subscription.Subscription{ + superduperchannelsubs[i] = &subscription.Subscription{ Channel: "TEST:" + strconv.FormatInt(int64(i), 10), - Pair: g.EnabledPairs[i], + Pairs: currency.Pairs{g.EnabledPairs[i]}, } } return superduperchannelsubs, nil } -func (g *GenSubs) SUBME(subs []subscription.Subscription) error { +func (g *GenSubs) SUBME(subs subscription.List) error { if len(subs) == 0 { return errors.New("WOW") } @@ -1017,7 +982,7 @@ func (g *GenSubs) SUBME(subs []subscription.Subscription) error { return nil } -func (g *GenSubs) UNSUBME(unsubs []subscription.Subscription) error { +func (g *GenSubs) UNSUBME(unsubs subscription.List) error { if len(unsubs) == 0 { return errors.New("WOW") } @@ -1044,87 +1009,70 @@ func TestFlushChannels(t *testing.T) { err = dodgyWs.FlushChannels() assert.ErrorIs(t, err, ErrNotConnected, "FlushChannels should error correctly") - w := Websocket{ - connector: connect, - ShutdownC: make(chan struct{}), - Subscriber: newgen.SUBME, - Unsubscriber: newgen.UNSUBME, - Wg: new(sync.WaitGroup), - features: &protocol.Features{ - // No features - }, - trafficTimeout: time.Second * 30, // Added for when we utilise connect() - // in FlushChannels() so the traffic monitor doesn't time out and turn - // this to an unconnected state - } + w := NewWebsocket() + w.connector = connect + w.Subscriber = newgen.SUBME + w.Unsubscriber = newgen.UNSUBME + // Added for when we utilise connect() in FlushChannels() so the traffic monitor doesn't time out and turn this to an unconnected state + w.trafficTimeout = time.Second * 30 + w.setEnabled(true) - w.setState(connected) + w.setState(connectedState) - problemFunc := func() ([]subscription.Subscription, error) { + problemFunc := func() (subscription.List, error) { return nil, errDastardlyReason } - noSub := func() ([]subscription.Subscription, error) { + noSub := func() (subscription.List, error) { return nil, nil } // Disable pair and flush system newgen.EnabledPairs = []currency.Pair{ currency.NewPair(currency.BTC, currency.AUD)} - w.GenerateSubs = func() ([]subscription.Subscription, error) { - return []subscription.Subscription{{Channel: "test"}}, nil + w.GenerateSubs = func() (subscription.List, error) { + return subscription.List{{Channel: "test"}}, nil } err = w.FlushChannels() - assert.NoError(t, err, "FlushChannels should not error") + require.NoError(t, err, "Flush Channels must not error") w.features.FullPayloadSubscribe = true w.GenerateSubs = problemFunc err = w.FlushChannels() // error on full subscribeToChannels - assert.ErrorIs(t, err, errDastardlyReason, "FlushChannels should error correctly") + assert.ErrorIs(t, err, errDastardlyReason, "FlushChannels should error correctly on GenerateSubs") w.GenerateSubs = noSub - err = w.FlushChannels() // No subs to unsub - assert.NoError(t, err, "FlushChannels should not error") + err = w.FlushChannels() // No subs to sub + assert.NoError(t, err, "Flush Channels should not error") w.GenerateSubs = newgen.generateSubs subs, err := w.GenerateSubs() require.NoError(t, err, "GenerateSubs must not error") - - w.AddSuccessfulSubscriptions(subs...) + require.NoError(t, w.AddSubscriptions(subs...), "AddSubscriptions must not error") err = w.FlushChannels() assert.NoError(t, err, "FlushChannels should not error") w.features.FullPayloadSubscribe = false w.features.Subscribe = true - w.GenerateSubs = problemFunc - err = w.FlushChannels() - assert.ErrorIs(t, err, errDastardlyReason, "FlushChannels should error correctly") - w.GenerateSubs = newgen.generateSubs - err = w.FlushChannels() - assert.NoError(t, err, "FlushChannels should not error") - w.subscriptionMutex.Lock() - w.subscriptions = subscriptionMap{ - 41: { - Key: 41, - Channel: "match channel", - Pair: currency.NewPair(currency.BTC, currency.AUD), - }, - 42: { - Key: 42, - Channel: "unsub channel", - Pair: currency.NewPair(currency.THETA, currency.USDT), - }, - } - w.subscriptionMutex.Unlock() - - err = w.FlushChannels() - assert.NoError(t, err, "FlushChannels should not error") + w.subscriptions = subscription.NewStore() + err = w.subscriptions.Add(&subscription.Subscription{ + Key: 41, + Channel: "match channel", + Pairs: currency.Pairs{currency.NewPair(currency.BTC, currency.AUD)}, + }) + require.NoError(t, err, "AddSubscription must not error") + err = w.subscriptions.Add(&subscription.Subscription{ + Key: 42, + Channel: "unsub channel", + Pairs: currency.Pairs{currency.NewPair(currency.THETA, currency.USDT)}, + }) + require.NoError(t, err, "AddSubscription must not error") err = w.FlushChannels() assert.NoError(t, err, "FlushChannels should not error") - w.setState(connected) + w.setState(connectedState) w.features.Unsubscribe = true err = w.FlushChannels() assert.NoError(t, err, "FlushChannels should not error") @@ -1132,27 +1080,20 @@ func TestFlushChannels(t *testing.T) { func TestDisable(t *testing.T) { t.Parallel() - w := Websocket{ - ShutdownC: make(chan struct{}), - } + w := NewWebsocket() w.setEnabled(true) - w.setState(connected) + w.setState(connectedState) require.NoError(t, w.Disable(), "Disable must not error") assert.ErrorIs(t, w.Disable(), ErrAlreadyDisabled, "Disable should error correctly") } func TestEnable(t *testing.T) { t.Parallel() - w := Websocket{ - connector: connect, - Wg: new(sync.WaitGroup), - ShutdownC: make(chan struct{}), - GenerateSubs: func() ([]subscription.Subscription, error) { - return []subscription.Subscription{{Channel: "test"}}, nil - }, - Subscriber: func([]subscription.Subscription) error { return nil }, - } - + w := NewWebsocket() + w.connector = connect + w.Subscriber = func(subscription.List) error { return nil } + w.Unsubscriber = func(subscription.List) error { return nil } + w.GenerateSubs = func() (subscription.List, error) { return nil, nil } require.NoError(t, w.Enable(), "Enable must not error") assert.ErrorIs(t, w.Enable(), errWebsocketAlreadyEnabled, "Enable should error correctly") } @@ -1259,19 +1200,30 @@ func TestCheckSubscriptions(t *testing.T) { t.Parallel() ws := Websocket{} err := ws.checkSubscriptions(nil) - assert.ErrorIs(t, err, errNoSubscriptionsSupplied, "checkSubscriptions should error correctly") + assert.ErrorIs(t, err, common.ErrNilPointer, "checkSubscriptions should error correctly on nil w.subscriptions") + assert.ErrorContains(t, err, "Websocket.subscriptions", "checkSubscriptions should error giving context correctly on nil w.subscriptions") + + ws.subscriptions = subscription.NewStore() + err = ws.checkSubscriptions(nil) + assert.NoError(t, err, "checkSubscriptions should not error on a nil list") ws.MaxSubscriptionsPerConnection = 1 - err = ws.checkSubscriptions([]subscription.Subscription{{}, {}}) + err = ws.checkSubscriptions(subscription.List{{}}) + assert.NoError(t, err, "checkSubscriptions should not error when subscriptions is empty") + + ws.subscriptions = subscription.NewStore() + err = ws.checkSubscriptions(subscription.List{{}, {}}) assert.ErrorIs(t, err, errSubscriptionsExceedsLimit, "checkSubscriptions should error correctly") ws.MaxSubscriptionsPerConnection = 2 - ws.subscriptions = subscriptionMap{42: {Key: 42, Channel: "test"}} - err = ws.checkSubscriptions([]subscription.Subscription{{Key: 42, Channel: "test"}}) - assert.ErrorIs(t, err, errChannelAlreadySubscribed, "checkSubscriptions should error correctly") + ws.subscriptions = subscription.NewStore() + err = ws.subscriptions.Add(&subscription.Subscription{Key: 42, Channel: "test"}) + require.NoError(t, err, "Add subscription must not error") + err = ws.checkSubscriptions(subscription.List{{Key: 42, Channel: "test"}}) + assert.ErrorIs(t, err, subscription.ErrDuplicate, "checkSubscriptions should error correctly") - err = ws.checkSubscriptions([]subscription.Subscription{{}}) + err = ws.checkSubscriptions(subscription.List{{}}) assert.NoError(t, err, "checkSubscriptions should not error") } diff --git a/exchanges/stream/websocket_types.go b/exchanges/stream/websocket_types.go index a783d585a4e..5d0009a4642 100644 --- a/exchanges/stream/websocket_types.go +++ b/exchanges/stream/websocket_types.go @@ -22,13 +22,11 @@ const ( UnhandledMessage = " - Unhandled websocket message: " ) -type subscriptionMap map[any]*subscription.Subscription - const ( - uninitialised uint32 = iota - disconnected - connecting - connected + uninitialisedState uint32 = iota + disconnectedState + connectingState + connectedState ) // Websocket defines a return type for websocket connections via the interface @@ -52,20 +50,14 @@ type Websocket struct { m sync.Mutex connector func() error - subscriptionMutex sync.RWMutex - subscriptions subscriptionMap - Subscribe chan []subscription.Subscription - Unsubscribe chan []subscription.Subscription - - // Subscriber function for package defined websocket subscriber - // functionality - Subscriber func([]subscription.Subscription) error - // Unsubscriber function for packaged defined websocket unsubscriber - // functionality - Unsubscriber func([]subscription.Subscription) error - // GenerateSubs function for package defined websocket generate - // subscriptions functionality - GenerateSubs func() ([]subscription.Subscription, error) + subscriptions *subscription.Store + + // Subscriber function for exchange specific subscribe implementation + Subscriber func(subscription.List) error + // Subscriber function for exchange specific unsubscribe implementation + Unsubscriber func(subscription.List) error + // GenerateSubs function for exchange specific generating subscriptions from Features.Subscriptions, Pairs and Assets + GenerateSubs func() (subscription.List, error) DataHandler chan interface{} ToRoutine chan interface{} @@ -74,7 +66,7 @@ type Websocket struct { // shutdown synchronises shutdown event across routines ShutdownC chan struct{} - Wg *sync.WaitGroup + Wg sync.WaitGroup // Orderbook is a local buffer of orderbooks Orderbook buffer.Orderbook @@ -112,9 +104,9 @@ type WebsocketSetup struct { RunningURL string RunningURLAuth string Connector func() error - Subscriber func([]subscription.Subscription) error - Unsubscriber func([]subscription.Subscription) error - GenerateSubscriptions func() ([]subscription.Subscription, error) + Subscriber func(subscription.List) error + Unsubscriber func(subscription.List) error + GenerateSubscriptions func() (subscription.List, error) Features *protocol.Features // Local orderbook buffer config values diff --git a/exchanges/subscription/keys.go b/exchanges/subscription/keys.go new file mode 100644 index 00000000000..3dc3ffea641 --- /dev/null +++ b/exchanges/subscription/keys.go @@ -0,0 +1,88 @@ +package subscription + +import ( + "fmt" + + "github.com/thrasher-corp/gocryptotrader/currency" +) + +// MatchableKey interface should be implemented by Key types which want a more complex matching than a simple key equality check +// The Subscription method allows keys to compare against keys of other types +type MatchableKey interface { + Match(MatchableKey) bool + GetSubscription() *Subscription + String() string +} + +// ExactKey is key type for subscriptions where all the pairs in a Subscription must match exactly +type ExactKey struct { + *Subscription +} + +var _ MatchableKey = ExactKey{} // Enforce ExactKey must implement MatchableKey + +// GetSubscription returns the underlying subscription +func (k ExactKey) GetSubscription() *Subscription { + return k.Subscription +} + +// String implements Stringer; returns the Asset, Channel and Pairs +// Does not provide concurrency protection on the subscription it points to +func (k ExactKey) String() string { + s := k.Subscription + if s == nil { + return "Uninitialised ExactKey" + } + p := s.Pairs.Format(currency.PairFormat{Uppercase: true, Delimiter: "/"}) + return fmt.Sprintf("%s %s %s", s.Channel, s.Asset, p.Join()) +} + +// Match implements MatchableKey +// Returns true if the key fields exactly matches the subscription, including all Pairs +func (k ExactKey) Match(eachKey MatchableKey) bool { + if eachKey == nil { + return false + } + eachSub := eachKey.GetSubscription() + return eachSub != nil && + eachSub.Channel == k.Channel && + eachSub.Asset == k.Asset && + eachSub.Pairs.Equal(k.Pairs) && + eachSub.Levels == k.Levels && + eachSub.Interval == k.Interval +} + +// IgnoringPairsKey is a key type for finding subscriptions to group together for requests +type IgnoringPairsKey struct { + *Subscription +} + +var _ MatchableKey = IgnoringPairsKey{} // Enforce IgnoringPairsKey must implement MatchableKey + +// GetSubscription returns the underlying subscription +func (k IgnoringPairsKey) GetSubscription() *Subscription { + return k.Subscription +} + +// String implements Stringer; returns the asset and Channel name but no pairs +func (k IgnoringPairsKey) String() string { + s := k.Subscription + if s == nil { + return "Uninitialised IgnoringPairsKey" + } + return fmt.Sprintf("%s %s", s.Channel, s.Asset) +} + +// Match implements MatchableKey +func (k IgnoringPairsKey) Match(eachKey MatchableKey) bool { + if eachKey == nil { + return false + } + eachSub := eachKey.GetSubscription() + + return eachSub != nil && + eachSub.Channel == k.Channel && + eachSub.Asset == k.Asset && + eachSub.Levels == k.Levels && + eachSub.Interval == k.Interval +} diff --git a/exchanges/subscription/keys_test.go b/exchanges/subscription/keys_test.go new file mode 100644 index 00000000000..d4972bac97b --- /dev/null +++ b/exchanges/subscription/keys_test.go @@ -0,0 +1,110 @@ +package subscription + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/thrasher-corp/gocryptotrader/currency" + "github.com/thrasher-corp/gocryptotrader/exchanges/asset" + "github.com/thrasher-corp/gocryptotrader/exchanges/kline" +) + +// DummyKey is a test key type that ensures that cross compatible keys can be used +// It will panic if Match() is called +type DummyKey struct { + *Subscription + detonator testing.TB +} + +var _ MatchableKey = DummyKey{} // Enforce DummyKey must implement MatchableKey + +// GetSubscription returns the underlying subscription +func (k DummyKey) GetSubscription() *Subscription { + return k.Subscription +} + +// Match implements MatchableKey +func (k DummyKey) Match(_ MatchableKey) bool { + k.detonator.Fatal("DummyKey Match should never be called") + return false +} + +// TestExactKeyMatch exercises ExactKey.Match +func TestExactKeyMatch(t *testing.T) { + t.Parallel() + + key := &ExactKey{&Subscription{Channel: TickerChannel}} + try := &DummyKey{&Subscription{Channel: OrderbookChannel}, t} + + require.False(t, key.Match(nil), "Match on a nil must return false") + require.False(t, key.Match(try), "Gate 1: Match must reject a bad Channel") + try.Channel = TickerChannel + require.True(t, key.Match(try), "Gate 1: Match must accept a good Channel") + key.Asset = asset.Spot + require.False(t, key.Match(try), "Gate 2: Match must reject a bad Asset") + try.Asset = asset.Spot + require.True(t, key.Match(try), "Gate 2: Match must accept a good Asset") + key.Pairs = currency.Pairs{btcusdtPair} + require.False(t, key.Match(try), "Gate 3: Match must reject B empty Pairs when key has Pairs") + try.Pairs = currency.Pairs{btcusdtPair} + key.Pairs = nil + require.False(t, key.Match(try), "Gate 3: Match must reject B has Pairs when key has empty Pairs") + key.Pairs = currency.Pairs{btcusdtPair} + require.True(t, key.Match(try), "Gate 3: Match must accept matching pairs") + key.Pairs = currency.Pairs{ethusdcPair} + require.False(t, key.Match(try), "Gate 3: Match must reject when key.Pairs not matching") + try.Pairs = currency.Pairs{btcusdtPair, ethusdcPair} + require.False(t, key.Match(try), "Gate 3: Match must reject when key.Pairs is only a subset") + key.Pairs = currency.Pairs{ethusdcPair, btcusdtPair} + require.True(t, key.Match(try), "Gate 3: Match accept when Pairs match in different order") + key.Levels = 4 + require.False(t, key.Match(try), "Gate 4: Match must reject a bad Level") + try.Levels = 4 + require.True(t, key.Match(try), "Gate 4: Match must accept a good Level") + key.Interval = kline.FiveMin + require.False(t, key.Match(try), "Gate 5: Match must reject a bad Interval") + try.Interval = kline.FiveMin + require.True(t, key.Match(try), "Gate 5: Match must accept a good Interval") +} + +// TestExactKeyString exercises ExactKey.String +func TestExactKeyString(t *testing.T) { + t.Parallel() + key := &ExactKey{} + assert.Equal(t, "Uninitialised ExactKey", key.String()) + key = &ExactKey{&Subscription{Asset: asset.Spot, Channel: TickerChannel, Pairs: currency.Pairs{ethusdcPair, btcusdtPair}}} + assert.Equal(t, "ticker spot ETH/USDC,BTC/USDT", key.String()) +} + +// TestIgnoringPairsKeyMatch exercises IgnoringPairsKey.Match +func TestIgnoringPairsKeyMatch(t *testing.T) { + t.Parallel() + + key := &IgnoringPairsKey{&Subscription{Channel: TickerChannel, Pairs: currency.Pairs{btcusdtPair}}} + try := &DummyKey{&Subscription{Channel: OrderbookChannel, Pairs: currency.Pairs{ethusdcPair}}, t} + + require.False(t, key.Match(nil), "Match on a nil must return false") + require.False(t, key.Match(try), "Gate 1: Match must reject a bad Channel") + try.Channel = TickerChannel + require.True(t, key.Match(try), "Gate 1: Match must accept a good Channel") + key.Asset = asset.Spot + require.False(t, key.Match(try), "Gate 2: Match must reject a bad Asset") + try.Asset = asset.Spot + require.True(t, key.Match(try), "Gate 2: Match must accept a good Asset") + key.Levels = 4 + require.False(t, key.Match(try), "Gate 3: Match must reject a bad Level") + try.Levels = 4 + require.True(t, key.Match(try), "Gate 3: Match must accept a good Level") + key.Interval = kline.FiveMin + require.False(t, key.Match(try), "Gate 4: Match must reject a bad Interval") + try.Interval = kline.FiveMin + require.True(t, key.Match(try), "Gate 4: Match must accept a good Interval") +} + +// TestIgnoringPairsKeyString exercises IgnoringPairsKey.String +func TestIgnoringPairsKeyString(t *testing.T) { + t.Parallel() + key := &IgnoringPairsKey{&Subscription{Asset: asset.Spot, Channel: TickerChannel, Pairs: currency.Pairs{ethusdcPair, btcusdtPair}}} + assert.Equal(t, "ticker spot", key.String()) +} diff --git a/exchanges/subscription/list.go b/exchanges/subscription/list.go new file mode 100644 index 00000000000..74791035dd1 --- /dev/null +++ b/exchanges/subscription/list.go @@ -0,0 +1,32 @@ +package subscription + +import ( + "slices" +) + +// List is a container of subscription pointers +type List []*Subscription + +// Strings returns a sorted slice of subscriptions +func (l List) Strings() []string { + s := make([]string, len(l)) + for i := range l { + s[i] = l[i].String() + } + slices.Sort(s) + return s +} + +// GroupPairs groups subscriptions which are identical apart from the Pairs +// The returned List contains cloned Subscriptions, and the original Subscriptions are left alone +func (l List) GroupPairs() (n List) { + s := NewStore() + for _, sub := range l { + if found := s.match(&IgnoringPairsKey{sub}); found == nil { + s.unsafeAdd(sub.Clone()) + } else { + found.AddPairs(sub.Pairs...) + } + } + return s.List() +} diff --git a/exchanges/subscription/list_test.go b/exchanges/subscription/list_test.go new file mode 100644 index 00000000000..89a53da67a2 --- /dev/null +++ b/exchanges/subscription/list_test.go @@ -0,0 +1,47 @@ +package subscription + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/thrasher-corp/gocryptotrader/currency" + "github.com/thrasher-corp/gocryptotrader/exchanges/asset" +) + +// TestListStrings exercises List.Strings() +func TestListStrings(t *testing.T) { + l := List{ + &Subscription{ + Channel: TickerChannel, + Asset: asset.Spot, + Pairs: currency.Pairs{ethusdcPair, btcusdtPair}, + }, + &Subscription{ + Channel: OrderbookChannel, + Pairs: currency.Pairs{ethusdcPair}, + }, + } + exp := []string{"orderbook ETH/USDC", "ticker spot ETH/USDC,BTC/USDT"} + assert.ElementsMatch(t, exp, l.Strings(), "String must return correct sorted list") +} + +// TestListGroupPairs exercises List.GroupPairs() +func TestListGroupPairs(t *testing.T) { + l := List{ + {Asset: asset.Spot, Channel: TickerChannel, Pairs: currency.Pairs{ethusdcPair, btcusdtPair}}, + } + for _, c := range []string{TickerChannel, OrderbookChannel} { + for _, p := range []currency.Pair{ethusdcPair, btcusdtPair} { + l = append(l, &Subscription{ + Channel: c, + Asset: asset.Spot, + Pairs: currency.Pairs{p}, + }) + } + } + n := l.GroupPairs() + assert.Len(t, l, 5, "Orig list should not be changed") + assert.Len(t, n, 2, "New list should be grouped") + exp := []string{"ticker spot ETH/USDC,BTC/USDT", "orderbook spot ETH/USDC,BTC/USDT"} + assert.ElementsMatch(t, exp, n.Strings(), "String must return correct sorted list") +} diff --git a/exchanges/subscription/store.go b/exchanges/subscription/store.go new file mode 100644 index 00000000000..9e842f8a3c7 --- /dev/null +++ b/exchanges/subscription/store.go @@ -0,0 +1,208 @@ +package subscription + +import ( + "fmt" + "maps" + "sync" + + "github.com/thrasher-corp/gocryptotrader/common" +) + +// Store is a container of subscription pointers +type Store struct { + m map[any]*Subscription + mu sync.RWMutex +} + +// NewStore creates a ready to use store and should always be used +func NewStore() *Store { + return &Store{ + m: map[any]*Subscription{}, + } +} + +// NewStoreFromList creates a Store from a List +func NewStoreFromList(l List) (*Store, error) { + s := NewStore() + for _, sub := range l { + if sub == nil { + return nil, fmt.Errorf("%w: List parameter contains an nil element", common.ErrNilPointer) + } + if err := s.add(sub); err != nil { + return nil, err + } + } + return s, nil +} + +// Add adds a subscription to the store +// Key can be already set; if omitted EnsureKeyed will be used +// Errors if it already exists +func (s *Store) Add(sub *Subscription) error { + if s == nil { + return fmt.Errorf("%w: Add called on nil Store", common.ErrNilPointer) + } + if s.m == nil { + return fmt.Errorf("%w: Add called on an Uninitialised Store", common.ErrNilPointer) + } + if sub == nil { + return fmt.Errorf("%w: Subscription param", common.ErrNilPointer) + } + s.mu.Lock() + defer s.mu.Unlock() + return s.add(sub) +} + +// add adds a subscription to the store +// Key can be already set; if omitted EnsureKeyed will be used +// This method provides no locking protection +func (s *Store) add(sub *Subscription) error { + key := sub.EnsureKeyed() + if found := s.get(key); found != nil { + return fmt.Errorf("%w: %s", ErrDuplicate, sub) + } + s.m[key] = sub + return nil +} + +// unsafeAdd adds a subscription to the store without checking if it is a duplicate +// Key can be already set; if omitted EnsureKeyed will be used +// This method provides no locking protection +func (s *Store) unsafeAdd(sub *Subscription) { + key := sub.EnsureKeyed() + s.m[key] = sub +} + +// Get returns a pointer to a subscription or nil if not found +// If the key passed in is a Subscription then its Key will be used; which may be a pointer to itself. +// If key implements MatchableKey then key.Match will be used; Note that *Subscription implements MatchableKey +func (s *Store) Get(key any) *Subscription { + if s == nil || s.m == nil || key == nil { + return nil + } + s.mu.RLock() + defer s.mu.RUnlock() + return s.get(key) +} + +// get returns a pointer to subscription or nil if not found +// If the key passed in is a Subscription then its Key will be used; which may be a pointer to itself. +// If key implements MatchableKey then key.Match will be used; Note that *Subscription implements MatchableKey +// This method provides no locking protection +func (s *Store) get(key any) *Subscription { + switch v := key.(type) { + case Subscription: + key = v.EnsureKeyed() + case *Subscription: + key = v.EnsureKeyed() + } + + switch v := key.(type) { + case MatchableKey: + return s.match(v) + default: + return s.m[v] + } +} + +// Remove removes a subscription from the store +// If the key passed in is a Subscription then its Key will be used; which may be a pointer to itself. +// If key implements MatchableKey then key.Match will be used; Note that *Subscription implements MatchableKey +func (s *Store) Remove(key any) error { + if s == nil { + return fmt.Errorf("%w: Remove called on nil Store", common.ErrNilPointer) + } + if s.m == nil { + return fmt.Errorf("%w: Remove called on an Uninitialised Store", common.ErrNilPointer) + } + if key == nil { + return fmt.Errorf("%w: key param", common.ErrNilPointer) + } + s.mu.Lock() + defer s.mu.Unlock() + + if found := s.get(key); found != nil { + delete(s.m, found.Key) + return nil + } + + return ErrNotFound +} + +// List returns a slice of Subscriptions pointers +func (s *Store) List() List { + if s == nil || s.m == nil { + return List{} + } + s.mu.RLock() + defer s.mu.RUnlock() + subs := make(List, 0, len(s.m)) + for _, sub := range s.m { + subs = append(subs, sub) + } + return subs +} + +// Clear empties the subscription store +func (s *Store) Clear() { + if s == nil { + return + } + s.mu.Lock() + defer s.mu.Unlock() + if s.m == nil { + s.m = map[any]*Subscription{} + } + clear(s.m) +} + +// match returns the first subscription which matches the Key's Asset, Channel and Pairs +// If the key provided has: +// 1) Empty pairs then only Subscriptions without pairs will be considered +// 2) >=1 pairs then Subscriptions which contain all the pairs will be considered +// This method provides no locking protection +func (s *Store) match(key MatchableKey) *Subscription { + for eachKey, sub := range s.m { + if m, ok := eachKey.(MatchableKey); ok { + if key.Match(m) { + return sub + } + } + } + return nil +} + +// Diff returns a list of the added and missing subs from a new list +// The store Diff is invoked upon is read-lock protected +// The new store is assumed to be a new instance and enjoys no locking protection +func (s *Store) Diff(compare List) (added, removed List) { + if s == nil || s.m == nil { + return + } + s.mu.RLock() + defer s.mu.RUnlock() + removedMap := maps.Clone(s.m) + for _, sub := range compare { + if found := s.get(sub); found != nil { + delete(removedMap, found.Key) + } else { + added = append(added, sub) + } + } + + for _, c := range removedMap { + removed = append(removed, c) + } + + return +} + +// Len returns the number of subscriptions +func (s *Store) Len() int { + if s == nil { + return 0 + } + s.mu.RLock() + defer s.mu.RUnlock() + return len(s.m) +} diff --git a/exchanges/subscription/store_test.go b/exchanges/subscription/store_test.go new file mode 100644 index 00000000000..7e9644bcc02 --- /dev/null +++ b/exchanges/subscription/store_test.go @@ -0,0 +1,182 @@ +package subscription + +import ( + "maps" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/thrasher-corp/gocryptotrader/common" + "github.com/thrasher-corp/gocryptotrader/currency" +) + +// TestNewStore exercises NewStore +func TestNewStore(t *testing.T) { + s := NewStore() + require.IsType(t, &Store{}, s, "Must return a store ref") + require.NotNil(t, s.m, "storage map must be initialised") +} + +// TestNewStoreFromList exercises NewStoreFromList +func TestNewStoreFromList(t *testing.T) { + s, err := NewStoreFromList(List{}) + assert.NoError(t, err, "Should not error on empty list") + require.IsType(t, &Store{}, s, "Must return a store ref") + l := List{ + {Channel: OrderbookChannel}, + {Channel: TickerChannel}, + } + s, err = NewStoreFromList(l) + assert.NoError(t, err, "Should not error on empty list") + assert.Len(t, s.m, 2, "Map should have 2 values") + assert.NotNil(t, s.get(l[0]), "Should be able to get a list element") + + l = append(l, &Subscription{Channel: OrderbookChannel}) + _, err = NewStoreFromList(l) + assert.ErrorIs(t, err, ErrDuplicate, "Should error correctly on duplicates") + + l = List{nil, &Subscription{Channel: OrderbookChannel}} + _, err = NewStoreFromList(l) + assert.ErrorIs(t, err, common.ErrNilPointer, "Should error correctly on nils") +} + +// TestAdd exercises Add and add methods +func TestAdd(t *testing.T) { + assert.ErrorIs(t, (*Store)(nil).Add(&Subscription{}), common.ErrNilPointer, "Should error nil pointer correctly") + assert.ErrorIs(t, (&Store{}).Add(nil), common.ErrNilPointer, "Should error nil pointer correctly") + assert.ErrorIs(t, (&Store{}).Add(&Subscription{}), common.ErrNilPointer, "Should error nil pointer correctly") + + s := NewStore() + sub := &Subscription{Channel: TickerChannel} + require.NoError(t, s.Add(sub), "Should not error on a standard add") + assert.NotNil(t, s.get(sub), "Should have stored the sub") + assert.ErrorIs(t, s.Add(sub), ErrDuplicate, "Should error on duplicates") + assert.NotNil(t, sub.Key, sub, "Add should call EnsureKeyed") +} + +// TestGet exercises Get and get methods +// Ensures that key's Match is used, but does not exercise subscription.Match; See TestMatch for that coverage +func TestGet(t *testing.T) { + assert.Nil(t, (*Store)(nil).Get(&Subscription{}), "Should return nil when called on nil") + assert.Nil(t, (&Store{}).Get(&Subscription{}), "Should return nil when called with no subscription map") + s := NewStore() + exp := List{ + {Channel: AllOrdersChannel}, + {Channel: TickerChannel, Pairs: currency.Pairs{btcusdtPair}}, + {Key: 42, Channel: OrderbookChannel}, + {Channel: CandlesChannel, Pairs: currency.Pairs{btcusdtPair, ethusdcPair}}, + } + for _, sub := range exp { + require.NoError(t, s.Add(sub), "Adding subscription must not error)") + } + + // Tests for a MatchableKey, ensuring that ExactKey works + assert.Nil(t, s.Get(Subscription{Channel: CandlesChannel}), "Should return nil without pairs") + assert.Nil(t, s.Get(Subscription{Channel: CandlesChannel, Pairs: currency.Pairs{ltcusdcPair}}), "Should return nil with wrong pair") + assert.Nil(t, s.Get(Subscription{Channel: CandlesChannel, Pairs: currency.Pairs{btcusdtPair}}), "Should return nil with only one right pair") + assert.Same(t, exp[3], s.Get(Subscription{Channel: CandlesChannel, Pairs: currency.Pairs{btcusdtPair, ethusdcPair}}), "Should return pointer when all pairs match") + assert.Nil(t, s.Get(Subscription{Channel: CandlesChannel, Pairs: currency.Pairs{btcusdtPair, ethusdcPair, ltcusdcPair}}), "Should return nil when key is superset of pairs") +} + +// TestRemove exercises the Remove method +func TestRemove(t *testing.T) { + assert.ErrorIs(t, (*Store)(nil).Remove(&Subscription{}), common.ErrNilPointer, "Should error correctly when called on nil") + assert.ErrorIs(t, (&Store{}).Remove(nil), common.ErrNilPointer, "Should error correctly when called passing nil") + assert.ErrorIs(t, (&Store{}).Remove(&Subscription{}), common.ErrNilPointer, "Should error correctly when called with no subscription map") + + s := NewStore() + require.NoError(t, s.Add(&Subscription{Channel: CandlesChannel, Pairs: currency.Pairs{btcusdtPair, ethusdcPair}}), "Adding subscription must not error") + assert.NotNil(t, s.Get(&ExactKey{&Subscription{Channel: CandlesChannel, Pairs: currency.Pairs{btcusdtPair, ethusdcPair}}}), "Should have added the sub") + assert.ErrorIs(t, s.Remove(&ExactKey{&Subscription{Channel: CandlesChannel, Pairs: currency.Pairs{btcusdtPair}}}), ErrNotFound, "Should error correctly when called with a non-matching key") + assert.NoError(t, s.Remove(&ExactKey{&Subscription{Channel: CandlesChannel, Pairs: currency.Pairs{btcusdtPair, ethusdcPair}}}), "Should not error when called with a matching key") + assert.Nil(t, s.Get(&ExactKey{&Subscription{Channel: CandlesChannel, Pairs: currency.Pairs{btcusdtPair, ethusdcPair}}}), "Should have removed the sub") + assert.ErrorIs(t, s.Remove(&ExactKey{&Subscription{Channel: CandlesChannel, Pairs: currency.Pairs{btcusdtPair, ethusdcPair}}}), ErrNotFound, "Should error correctly when called twice ") +} + +// TestList exercises the List and Len methods +func TestList(t *testing.T) { + assert.Empty(t, (*Store)(nil).List(), "Should return an empty List when called on nil") + assert.Empty(t, (&Store{}).List(), "Should return an empty List when called on Store without map") + s := NewStore() + exp := List{ + {Channel: OrderbookChannel}, + {Channel: TickerChannel}, + {Key: 42, Channel: CandlesChannel}, + } + for _, sub := range exp { + require.NoError(t, s.Add(sub), "Adding subscription must not error)") + } + l := s.List() + require.Len(t, l, 3, "Must have 3 elements in the list") + assert.ElementsMatch(t, exp, l, "List Should have the same subscriptions") + + require.Equal(t, 3, s.Len(), "Len must return 3") + require.Equal(t, 0, (*Store)(nil).Len(), "Len must return 0 on a nil store") + require.Equal(t, 0, (&Store{}).Len(), "Len must return 0 on an uninitialized store") +} + +// TestStoreClear exercises the Clear method +func TestStoreClear(t *testing.T) { + assert.NotPanics(t, func() { (*Store)(nil).Clear() }, "Should not panic when called on nil") + s := &Store{} + assert.NotPanics(t, func() { s.Clear() }, "Should not panic when called with no subscription map") + assert.NotNil(t, s.m, "Should create a map when called on an empty Store") + require.NoError(t, s.Add(&Subscription{Channel: CandlesChannel}), "Adding subscription must not error") + require.Len(t, s.m, 1, "Must have a subscription") + s.Clear() + require.Empty(t, s.m, "Map must be empty after clearing") + assert.NotPanics(t, func() { s.Clear() }, "Should not panic when called on an empty map") +} + +// TestStoreDiff exercises the Diff method +func TestStoreDiff(t *testing.T) { + s := NewStore() + assert.NotPanics(t, func() { (*Store)(nil).Diff(List{}) }, "Should not panic when called on nil") + assert.NotPanics(t, func() { (&Store{}).Diff(List{}) }, "Should not panic when called with no subscription map") + subs, unsubs := s.Diff(List{{Channel: TickerChannel}, {Channel: CandlesChannel}, {Channel: OrderbookChannel}}) + assert.Equal(t, 3, len(subs), "Should get the correct number of subs") + assert.Empty(t, unsubs, "Should get no unsubs") + for _, sub := range subs { + require.NoError(t, s.add(sub), "add must not error") + } + assert.NotPanics(t, func() { s.Diff(nil) }, "Should not panic when called with nil list") + + subs, unsubs = s.Diff(List{{Channel: CandlesChannel}}) + assert.Empty(t, subs, "Should get no subs") + assert.Equal(t, 2, len(unsubs), "Should get the correct number of unsubs") + subs, unsubs = s.Diff(List{{Channel: TickerChannel}, {Channel: MyTradesChannel}}) + require.Equal(t, 1, len(subs), "Should get the correct number of subs") + assert.Equal(t, MyTradesChannel, subs[0].Channel, "Should get correct channels in sub") + require.Equal(t, 2, len(unsubs), "Should get the correct number of unsubs") + EqualLists(t, unsubs, List{{Channel: OrderbookChannel}, {Channel: CandlesChannel}}) +} + +func EqualLists(tb testing.TB, a, b List) { + tb.Helper() + // Must not use store.Diff directly + s, err := NewStoreFromList(a) + require.NoError(tb, err, "NewStoreFromList must not error") + missingMap := maps.Clone(s.m) + var added, missing List + for _, sub := range b { + if found := s.get(sub); found != nil { + delete(missingMap, found.Key) + } else { + added = append(added, sub) + } + } + for _, c := range missingMap { + missing = append(missing, c) + } + if len(added) > 0 || len(missing) > 0 { + fail := "Differences:" + if len(added) > 0 { + fail = fail + "\n + " + strings.Join(added.Strings(), "\n + ") + } + if len(missing) > 0 { + fail = fail + "\n - " + strings.Join(missing.Strings(), "\n - ") + } + assert.Fail(tb, fail, "Subscriptions should be equal") + } +} diff --git a/exchanges/subscription/subscription.go b/exchanges/subscription/subscription.go index 874822ba79a..9e6f560d253 100644 --- a/exchanges/subscription/subscription.go +++ b/exchanges/subscription/subscription.go @@ -1,92 +1,163 @@ package subscription import ( - "encoding/json" + "errors" "fmt" + "maps" + "slices" + "sync" "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/kline" ) -// DefaultKey is the fallback key for AddSuccessfulSubscriptions -type DefaultKey struct { - Channel string - Pair currency.Pair - Asset asset.Item -} - -// State tracks the status of a subscription channel -type State uint8 +// State constants +const ( + InactiveState State = iota + SubscribingState + SubscribedState + ResubscribingState + UnsubscribingState + UnsubscribedState +) +// Channel constants const ( - UnknownState State = iota // UnknownState subscription state is not registered, but doesn't imply Inactive - SubscribingState // SubscribingState means channel is in the process of subscribing - SubscribedState // SubscribedState means the channel has finished a successful and acknowledged subscription - UnsubscribingState // UnsubscribingState means the channel has started to unsubscribe, but not yet confirmed + TickerChannel = "ticker" + OrderbookChannel = "orderbook" + CandlesChannel = "candles" + AllOrdersChannel = "allOrders" + AllTradesChannel = "allTrades" + MyTradesChannel = "myTrades" + MyOrdersChannel = "myOrders" +) - TickerChannel = "ticker" // TickerChannel Subscription Type - OrderbookChannel = "orderbook" // OrderbookChannel Subscription Type - CandlesChannel = "candles" // CandlesChannel Subscription Type - AllOrdersChannel = "allOrders" // AllOrdersChannel Subscription Type - AllTradesChannel = "allTrades" // AllTradesChannel Subscription Type - MyTradesChannel = "myTrades" // MyTradesChannel Subscription Type - MyOrdersChannel = "myOrders" // MyOrdersChannel Subscription Type +// Public errors +var ( + ErrNotFound = errors.New("subscription not found") + ErrNotSinglePair = errors.New("only single pair subscriptions expected") + ErrInStateAlready = errors.New("subscription already in state") + ErrInvalidState = errors.New("invalid subscription state") + ErrDuplicate = errors.New("duplicate subscription") ) +// State tracks the status of a subscription channel +type State uint8 + // Subscription container for streaming subscriptions type Subscription struct { - Enabled bool `json:"enabled"` - Key any `json:"-"` - Channel string `json:"channel,omitempty"` - Pair currency.Pair `json:"pair,omitempty"` - Asset asset.Item `json:"asset,omitempty"` - Params map[string]interface{} `json:"params,omitempty"` - State State `json:"-"` - Interval kline.Interval `json:"interval,omitempty"` - Levels int `json:"levels,omitempty"` - Authenticated bool `json:"authenticated,omitempty"` + Enabled bool `json:"enabled"` + Key any `json:"-"` + Channel string `json:"channel,omitempty"` + Pairs currency.Pairs `json:"pairs,omitempty"` + Asset asset.Item `json:"asset,omitempty"` + Params map[string]any `json:"params,omitempty"` + Interval kline.Interval `json:"interval,omitempty"` + Levels int `json:"levels,omitempty"` + Authenticated bool `json:"authenticated,omitempty"` + state State + m sync.RWMutex } -// MarshalJSON generates a JSON representation of a Subscription, specifically for config writing -// The only reason it exists is to avoid having to make Pair a pointer, since that would be generally painful -// If Pair becomes a pointer, this method is redundant and should be removed -func (s *Subscription) MarshalJSON() ([]byte, error) { - // None of the usual type embedding tricks seem to work for not emitting an nil Pair - // The embedded type's Pair always fills the empty value - type MaybePair struct { - Enabled bool `json:"enabled"` - Channel string `json:"channel,omitempty"` - Asset asset.Item `json:"asset,omitempty"` - Params map[string]interface{} `json:"params,omitempty"` - Interval kline.Interval `json:"interval,omitempty"` - Levels int `json:"levels,omitempty"` - Authenticated bool `json:"authenticated,omitempty"` - Pair *currency.Pair `json:"pair,omitempty"` +// String implements Stringer, and aims to informatively and uniquely identify a subscription for errors and information +// returns a string of the subscription key by delegating to MatchableKey.String() when possible +// If the key is not a MatchableKey then both the key and an ExactKey.String() will be returned; e.g. 1137: spot MyTrades +func (s *Subscription) String() string { + key := s.EnsureKeyed() + s.m.RLock() + defer s.m.RUnlock() + if k, ok := key.(MatchableKey); ok { + return k.String() } + return fmt.Sprintf("%v: %s", key, ExactKey{s}.String()) +} - k := MaybePair{s.Enabled, s.Channel, s.Asset, s.Params, s.Interval, s.Levels, s.Authenticated, nil} - if s.Pair != currency.EMPTYPAIR { - k.Pair = &s.Pair - } +// State returns the subscription state +func (s *Subscription) State() State { + s.m.RLock() + defer s.m.RUnlock() + return s.state +} - return json.Marshal(k) +// SetState sets the subscription state +// Errors if already in that state or the new state is not valid +func (s *Subscription) SetState(state State) error { + s.m.Lock() + defer s.m.Unlock() + if state == s.state { + return ErrInStateAlready + } + if state > UnsubscribedState { + return ErrInvalidState + } + s.state = state + return nil } -// String implements the Stringer interface for Subscription, giving a human representation of the subscription -func (s *Subscription) String() string { - return fmt.Sprintf("%s %s %s", s.Channel, s.Asset, s.Pair) +// SetKey does what it says on the tin safely for concurrency +func (s *Subscription) SetKey(key any) { + s.m.Lock() + defer s.m.Unlock() + s.Key = key } -// EnsureKeyed sets the default key on a channel if it doesn't have one -// Returns key for convenience +// EnsureKeyed returns the subscription key +// If no key exists then ExactKey will be used func (s *Subscription) EnsureKeyed() any { - if s.Key == nil { - s.Key = DefaultKey{ - Channel: s.Channel, - Asset: s.Asset, - Pair: s.Pair, - } + // Juggle RLock/WLock to minimize concurrent bottleneck for hottest path + s.m.RLock() + if s.Key != nil { + defer s.m.RUnlock() + return s.Key + } + s.m.RUnlock() + s.m.Lock() + defer s.m.Unlock() + if s.Key == nil { // Ensure race hasn't updated Key whilst we swapped locks + s.Key = &ExactKey{s} } return s.Key } + +// Clone returns a copy of a subscription +// Key is set to nil, because most Key types contain a pointer to the subscription, and because the clone isn't added to the store yet +// Users should allow a default key to be assigned on AddSubscription or can SetKey as necessary +func (s *Subscription) Clone() *Subscription { + s.m.RLock() + c := &Subscription{ + Key: nil, + Enabled: s.Enabled, + Channel: s.Channel, + Asset: s.Asset, + Params: s.Params, + Interval: s.Interval, + Levels: s.Levels, + Authenticated: s.Authenticated, + state: s.state, + Pairs: s.Pairs, + } + s.Pairs = slices.Clone(s.Pairs) + s.Params = maps.Clone(s.Params) + s.m.RUnlock() + return c +} + +// SetPairs does what it says on the tin safely for concurrency +func (s *Subscription) SetPairs(pairs currency.Pairs) { + s.m.Lock() + s.Pairs = pairs + s.m.Unlock() +} + +// AddPairs does what it says on the tin safely for concurrency +func (s *Subscription) AddPairs(pairs ...currency.Pair) { + if len(pairs) == 0 { + return + } + s.m.Lock() + for _, p := range pairs { + s.Pairs = s.Pairs.Add(p) + } + s.m.Unlock() +} diff --git a/exchanges/subscription/subscription_test.go b/exchanges/subscription/subscription_test.go index 4f9a97ab979..427e68f6851 100644 --- a/exchanges/subscription/subscription_test.go +++ b/exchanges/subscription/subscription_test.go @@ -10,39 +10,80 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/kline" ) -// TestEnsureKeyed logic test -func TestEnsureKeyed(t *testing.T) { +var ( + btcusdtPair = currency.NewPair(currency.BTC, currency.USDT) + ethusdcPair = currency.NewPair(currency.ETH, currency.USDC) + ltcusdcPair = currency.NewPair(currency.LTC, currency.USDC) +) + +// TestSubscriptionString exercises the String method +func TestSubscriptionString(t *testing.T) { + s := &Subscription{ + Channel: "candles", + Asset: asset.Spot, + Pairs: currency.Pairs{btcusdtPair, ethusdcPair.Format(currency.PairFormat{Delimiter: "/"})}, + } + assert.Equal(t, "candles spot BTC/USDT,ETH/USDC", s.String(), "Subscription String should return correct value") +} + +// TestState exercises the state getter +func TestState(t *testing.T) { + t.Parallel() + s := &Subscription{} + assert.Equal(t, InactiveState, s.State(), "State should return initial state") + s.state = SubscribedState + assert.Equal(t, SubscribedState, s.State(), "State should return correct state") +} + +// TestSetState exercises the state setter +func TestSetState(t *testing.T) { t.Parallel() - c := Subscription{ + + s := &Subscription{state: UnsubscribedState} + + for i := InactiveState; i <= UnsubscribedState; i++ { + assert.NoErrorf(t, s.SetState(i), "State should not error setting state %s", i) + } + assert.ErrorIs(t, s.SetState(UnsubscribedState), ErrInStateAlready, "SetState should error on same state") + assert.ErrorIs(t, s.SetState(UnsubscribedState+1), ErrInvalidState, "Setting an invalid state should error") +} + +// TestString exercises the Stringer implementation +func TestString(t *testing.T) { + s := &Subscription{ Channel: "candles", Asset: asset.Spot, - Pair: currency.NewPair(currency.BTC, currency.USDT), + Pairs: currency.Pairs{btcusdtPair}, } - k1, ok := c.EnsureKeyed().(DefaultKey) - if assert.True(t, ok, "EnsureKeyed should return a DefaultKey") { - assert.Exactly(t, k1, c.Key, "EnsureKeyed should set the same key") - assert.Equal(t, k1.Channel, c.Channel, "DefaultKey channel should be correct") - assert.Equal(t, k1.Asset, c.Asset, "DefaultKey asset should be correct") - assert.Equal(t, k1.Pair, c.Pair, "DefaultKey currency should be correct") + _ = s.EnsureKeyed() + assert.Equal(t, "candles spot BTC/USDT", s.String(), "String with a MatchableKey") + s.Key = 42 + assert.Equal(t, "42: candles spot BTC/USDT", s.String(), "String with a MatchableKey") +} + +// TestEnsureKeyed exercises the key getter and ensures it sets a self-pointer key for non +func TestEnsureKeyed(t *testing.T) { + t.Parallel() + s := &Subscription{} + k1, ok := s.EnsureKeyed().(MatchableKey) + if assert.True(t, ok, "EnsureKeyed should return a MatchableKey") { + assert.Same(t, s, k1.GetSubscription(), "Key should point to the same struct") } type platypus string - c = Subscription{ + s = &Subscription{ Key: platypus("Gerald"), Channel: "orderbook", - Asset: asset.Margin, - Pair: currency.NewPair(currency.ETH, currency.USDC), - } - k2, ok := c.EnsureKeyed().(platypus) - if assert.True(t, ok, "EnsureKeyed should return a platypus") { - assert.Exactly(t, k2, c.Key, "EnsureKeyed should set the same key") - assert.EqualValues(t, "Gerald", k2, "key should have the correct value") } + k2 := s.EnsureKeyed() + assert.IsType(t, platypus(""), k2, "EnsureKeyed should return a platypus") + assert.Equal(t, s.Key, k2, "Key should be the key provided") } -// TestMarshalling logic test -func TestMarshaling(t *testing.T) { +// TestSubscriptionMarshalling ensures json Marshalling is clean and concise +// Since there is no UnmarshalJSON, this just exercises the json field tags of Subscription, and regressions in conciseness +func TestSubscriptionMarshaling(t *testing.T) { t.Parallel() - j, err := json.Marshal(&Subscription{Channel: CandlesChannel}) + j, err := json.Marshal(&Subscription{Key: 42, Channel: CandlesChannel}) assert.NoError(t, err, "Marshalling should not error") assert.Equal(t, `{"enabled":false,"channel":"candles"}`, string(j), "Marshalling should be clean and concise") @@ -50,11 +91,46 @@ func TestMarshaling(t *testing.T) { assert.NoError(t, err, "Marshalling should not error") assert.Equal(t, `{"enabled":true,"channel":"orderbook","interval":"5m","levels":4}`, string(j), "Marshalling should be clean and concise") - j, err = json.Marshal(&Subscription{Enabled: true, Channel: OrderbookChannel, Interval: kline.FiveMin, Levels: 4, Pair: currency.NewPair(currency.BTC, currency.USDT)}) + j, err = json.Marshal(&Subscription{Enabled: true, Channel: OrderbookChannel, Interval: kline.FiveMin, Levels: 4, Pairs: currency.Pairs{currency.NewPair(currency.BTC, currency.USDT)}}) assert.NoError(t, err, "Marshalling should not error") - assert.Equal(t, `{"enabled":true,"channel":"orderbook","interval":"5m","levels":4,"pair":"BTCUSDT"}`, string(j), "Marshalling should be clean and concise") + assert.Equal(t, `{"enabled":true,"channel":"orderbook","pairs":"BTCUSDT","interval":"5m","levels":4}`, string(j), "Marshalling should be clean and concise") j, err = json.Marshal(&Subscription{Enabled: true, Channel: MyTradesChannel, Authenticated: true}) assert.NoError(t, err, "Marshalling should not error") assert.Equal(t, `{"enabled":true,"channel":"myTrades","authenticated":true}`, string(j), "Marshalling should be clean and concise") } + +// TestClone exercises Clone +func TestClone(t *testing.T) { + a := &Subscription{ + Channel: TickerChannel, + Interval: kline.OneHour, + Pairs: currency.Pairs{btcusdtPair}, + Params: map[string]any{"a": 42}, + } + a.EnsureKeyed() + b := a.Clone() + assert.IsType(t, new(Subscription), b, "Clone must return a Subscription pointer") + assert.NotSame(t, a, b, "Clone should return a new Subscription") + assert.Nil(t, b.Key, "Clone should have a nil key") + b.Pairs[0] = ethusdcPair + assert.Equal(t, btcusdtPair, a.Pairs[0], "Pairs should be (relatively) deep copied") + b.Params["a"] = 12 + assert.Equal(t, 42, a.Params["a"], "Params should be (relatively) deep copied") + a.m.Lock() + assert.True(t, b.m.TryLock(), "Clone must use a different Mutex") +} + +// TestSetKey exercises SetKey +func TestSetKey(t *testing.T) { + s := &Subscription{} + s.SetKey(14) + assert.Equal(t, 14, s.Key, "SetKey should set a key correctly") +} + +// TestSetPairs exercises SetPairs +func TestSetPairs(t *testing.T) { + s := &Subscription{} + s.SetPairs(currency.Pairs{btcusdtPair}) + assert.Equal(t, "BTCUSDT", s.Pairs.Join(), "SetPairs should set a key correctly") +} diff --git a/gctrpc/rpc.pb.go b/gctrpc/rpc.pb.go index ec950d26269..d634a42d9fc 100644 --- a/gctrpc/rpc.pb.go +++ b/gctrpc/rpc.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.33.0 +// protoc-gen-go v1.34.1 // protoc (unknown) // source: rpc.proto @@ -8894,7 +8894,7 @@ type WebsocketSubscription struct { unknownFields protoimpl.UnknownFields Channel string `protobuf:"bytes,1,opt,name=channel,proto3" json:"channel,omitempty"` - Pair string `protobuf:"bytes,2,opt,name=pair,proto3" json:"pair,omitempty"` + Pairs string `protobuf:"bytes,2,opt,name=pairs,proto3" json:"pairs,omitempty"` Asset string `protobuf:"bytes,3,opt,name=asset,proto3" json:"asset,omitempty"` Params string `protobuf:"bytes,4,opt,name=params,proto3" json:"params,omitempty"` } @@ -8938,9 +8938,9 @@ func (x *WebsocketSubscription) GetChannel() string { return "" } -func (x *WebsocketSubscription) GetPair() string { +func (x *WebsocketSubscription) GetPairs() string { if x != nil { - return x.Pair + return x.Pairs } return "" } @@ -16908,670 +16908,687 @@ var file_rpc_proto_rawDesc = []byte{ 0x47, 0x65, 0x74, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, - 0x6e, 0x67, 0x65, 0x22, 0x73, 0x0a, 0x15, 0x57, 0x65, 0x62, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, + 0x6e, 0x67, 0x65, 0x22, 0x75, 0x0a, 0x15, 0x57, 0x65, 0x62, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, - 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x69, 0x72, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x69, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x73, - 0x73, 0x65, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, - 0x12, 0x16, 0x0a, 0x06, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x06, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x22, 0x84, 0x01, 0x0a, 0x21, 0x57, 0x65, 0x62, - 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x47, 0x65, 0x74, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, - 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, - 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x43, 0x0a, 0x0d, 0x73, 0x75, - 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x1d, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x65, 0x62, 0x73, 0x6f, - 0x63, 0x6b, 0x65, 0x74, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, - 0x52, 0x0d, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, - 0x4c, 0x0a, 0x18, 0x57, 0x65, 0x62, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x53, 0x65, 0x74, 0x50, - 0x72, 0x6f, 0x78, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x65, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x70, 0x61, 0x69, 0x72, 0x73, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x70, 0x61, 0x69, 0x72, 0x73, 0x12, 0x14, 0x0a, 0x05, + 0x61, 0x73, 0x73, 0x65, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x73, 0x73, + 0x65, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x22, 0x84, 0x01, 0x0a, 0x21, 0x57, + 0x65, 0x62, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x47, 0x65, 0x74, 0x53, 0x75, 0x62, 0x73, 0x63, + 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x43, 0x0a, 0x0d, + 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x65, 0x62, + 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, + 0x6f, 0x6e, 0x52, 0x0d, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x22, 0x4c, 0x0a, 0x18, 0x57, 0x65, 0x62, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x53, 0x65, + 0x74, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, + 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x70, 0x72, 0x6f, + 0x78, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x22, + 0x46, 0x0a, 0x16, 0x57, 0x65, 0x62, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x53, 0x65, 0x74, 0x55, + 0x52, 0x4c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, + 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, + 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x22, 0xd3, 0x01, 0x0a, 0x1f, 0x46, 0x69, 0x6e, 0x64, + 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x43, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x50, 0x65, 0x72, + 0x69, 0x6f, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x65, + 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0c, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4e, 0x61, 0x6d, 0x65, + 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x73, 0x73, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, + 0x28, 0x0a, 0x04, 0x70, 0x61, 0x69, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, + 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x50, + 0x61, 0x69, 0x72, 0x52, 0x04, 0x70, 0x61, 0x69, 0x72, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x6e, 0x74, + 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x69, 0x6e, 0x74, + 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x65, + 0x6e, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x65, 0x6e, 0x64, 0x22, 0xb6, 0x01, + 0x0a, 0x1e, 0x46, 0x69, 0x6e, 0x64, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x54, 0x72, 0x61, + 0x64, 0x65, 0x50, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x23, 0x0a, 0x0d, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x5f, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, + 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x74, + 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x73, 0x73, 0x65, 0x74, + 0x54, 0x79, 0x70, 0x65, 0x12, 0x28, 0x0a, 0x04, 0x70, 0x61, 0x69, 0x72, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x75, 0x72, 0x72, + 0x65, 0x6e, 0x63, 0x79, 0x50, 0x61, 0x69, 0x72, 0x52, 0x04, 0x70, 0x61, 0x69, 0x72, 0x12, 0x14, + 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, + 0x74, 0x61, 0x72, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x03, 0x65, 0x6e, 0x64, 0x22, 0xcd, 0x01, 0x0a, 0x1c, 0x46, 0x69, 0x6e, 0x64, 0x4d, + 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x65, 0x78, 0x63, 0x68, 0x61, + 0x6e, 0x67, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, + 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1d, 0x0a, 0x0a, + 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x09, 0x61, 0x73, 0x73, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x28, 0x0a, 0x04, 0x70, + 0x61, 0x69, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x63, 0x74, 0x72, + 0x70, 0x63, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x50, 0x61, 0x69, 0x72, 0x52, + 0x04, 0x70, 0x61, 0x69, 0x72, 0x12, 0x27, 0x0a, 0x0f, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, + 0x5f, 0x70, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e, + 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x50, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x73, 0x12, 0x16, + 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x57, 0x0a, 0x21, 0x53, 0x65, 0x74, 0x45, 0x78, 0x63, + 0x68, 0x61, 0x6e, 0x67, 0x65, 0x54, 0x72, 0x61, 0x64, 0x65, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, + 0x73, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, - 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x70, 0x72, 0x6f, 0x78, 0x79, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x22, 0x46, 0x0a, - 0x16, 0x57, 0x65, 0x62, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x53, 0x65, 0x74, 0x55, 0x52, 0x4c, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, - 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, - 0x6e, 0x67, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x03, 0x75, 0x72, 0x6c, 0x22, 0xd3, 0x01, 0x0a, 0x1f, 0x46, 0x69, 0x6e, 0x64, 0x4d, 0x69, - 0x73, 0x73, 0x69, 0x6e, 0x67, 0x43, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x50, 0x65, 0x72, 0x69, 0x6f, - 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x65, 0x78, 0x63, - 0x68, 0x61, 0x6e, 0x67, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0c, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1d, - 0x0a, 0x0a, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x09, 0x61, 0x73, 0x73, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x28, 0x0a, - 0x04, 0x70, 0x61, 0x69, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x63, + 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, + 0xa3, 0x06, 0x0a, 0x1b, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x44, 0x61, 0x74, 0x61, 0x48, 0x69, + 0x73, 0x74, 0x6f, 0x72, 0x79, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x1a, 0x0a, 0x08, 0x6e, 0x69, 0x63, 0x6b, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x08, 0x6e, 0x69, 0x63, 0x6b, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x65, + 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, + 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x12, 0x28, 0x0a, + 0x04, 0x70, 0x61, 0x69, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x50, 0x61, 0x69, - 0x72, 0x52, 0x04, 0x70, 0x61, 0x69, 0x72, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, - 0x76, 0x61, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, - 0x76, 0x61, 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x05, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x65, 0x6e, 0x64, - 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x65, 0x6e, 0x64, 0x22, 0xb6, 0x01, 0x0a, 0x1e, - 0x46, 0x69, 0x6e, 0x64, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x54, 0x72, 0x61, 0x64, 0x65, - 0x50, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x23, - 0x0a, 0x0d, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4e, - 0x61, 0x6d, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x74, 0x79, 0x70, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x73, 0x73, 0x65, 0x74, 0x54, 0x79, - 0x70, 0x65, 0x12, 0x28, 0x0a, 0x04, 0x70, 0x61, 0x69, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x14, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, - 0x63, 0x79, 0x50, 0x61, 0x69, 0x72, 0x52, 0x04, 0x70, 0x61, 0x69, 0x72, 0x12, 0x14, 0x0a, 0x05, - 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, - 0x72, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x03, 0x65, 0x6e, 0x64, 0x22, 0xcd, 0x01, 0x0a, 0x1c, 0x46, 0x69, 0x6e, 0x64, 0x4d, 0x69, 0x73, - 0x73, 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, - 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x65, 0x78, - 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x73, - 0x73, 0x65, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, - 0x61, 0x73, 0x73, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x28, 0x0a, 0x04, 0x70, 0x61, 0x69, - 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, - 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x50, 0x61, 0x69, 0x72, 0x52, 0x04, 0x70, - 0x61, 0x69, 0x72, 0x12, 0x27, 0x0a, 0x0f, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x5f, 0x70, - 0x65, 0x72, 0x69, 0x6f, 0x64, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e, 0x6d, 0x69, - 0x73, 0x73, 0x69, 0x6e, 0x67, 0x50, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x73, 0x12, 0x16, 0x0a, 0x06, - 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x22, 0x57, 0x0a, 0x21, 0x53, 0x65, 0x74, 0x45, 0x78, 0x63, 0x68, 0x61, - 0x6e, 0x67, 0x65, 0x54, 0x72, 0x61, 0x64, 0x65, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x69, - 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, - 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, - 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0xa3, 0x06, - 0x0a, 0x1b, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x44, 0x61, 0x74, 0x61, 0x48, 0x69, 0x73, 0x74, - 0x6f, 0x72, 0x79, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, - 0x08, 0x6e, 0x69, 0x63, 0x6b, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x72, 0x52, 0x04, 0x70, 0x61, 0x69, 0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, + 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x74, 0x61, + 0x72, 0x74, 0x44, 0x61, 0x74, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x5f, 0x64, 0x61, + 0x74, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x65, 0x6e, 0x64, 0x44, 0x61, 0x74, + 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x07, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x2c, 0x0a, + 0x12, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x5f, 0x6c, 0x69, + 0x6d, 0x69, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x72, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x53, 0x69, 0x7a, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x64, + 0x61, 0x74, 0x61, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, + 0x64, 0x61, 0x74, 0x61, 0x54, 0x79, 0x70, 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x6d, 0x61, 0x78, 0x5f, + 0x72, 0x65, 0x74, 0x72, 0x79, 0x5f, 0x61, 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x73, 0x18, 0x0a, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x6d, 0x61, 0x78, 0x52, 0x65, 0x74, 0x72, 0x79, 0x41, 0x74, + 0x74, 0x65, 0x6d, 0x70, 0x74, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x61, 0x74, 0x63, 0x68, 0x5f, + 0x73, 0x69, 0x7a, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x62, 0x61, 0x74, 0x63, + 0x68, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x5f, + 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x69, 0x6e, 0x73, 0x65, + 0x72, 0x74, 0x4f, 0x6e, 0x6c, 0x79, 0x12, 0x2f, 0x0a, 0x13, 0x63, 0x6f, 0x6e, 0x76, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x0d, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x12, 0x63, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x49, + 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x36, 0x0a, 0x17, 0x6f, 0x76, 0x65, 0x72, 0x77, + 0x72, 0x69, 0x74, 0x65, 0x5f, 0x65, 0x78, 0x69, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x64, 0x61, + 0x74, 0x61, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x08, 0x52, 0x15, 0x6f, 0x76, 0x65, 0x72, 0x77, 0x72, + 0x69, 0x74, 0x65, 0x45, 0x78, 0x69, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x44, 0x61, 0x74, 0x61, 0x12, + 0x3a, 0x0a, 0x19, 0x70, 0x72, 0x65, 0x72, 0x65, 0x71, 0x75, 0x69, 0x73, 0x69, 0x74, 0x65, 0x5f, + 0x6a, 0x6f, 0x62, 0x5f, 0x6e, 0x69, 0x63, 0x6b, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x0f, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x17, 0x70, 0x72, 0x65, 0x72, 0x65, 0x71, 0x75, 0x69, 0x73, 0x69, 0x74, 0x65, + 0x4a, 0x6f, 0x62, 0x4e, 0x69, 0x63, 0x6b, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x38, 0x0a, 0x18, 0x64, + 0x65, 0x63, 0x69, 0x6d, 0x61, 0x6c, 0x5f, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x5f, 0x63, 0x6f, 0x6d, + 0x70, 0x61, 0x72, 0x69, 0x73, 0x6f, 0x6e, 0x18, 0x10, 0x20, 0x01, 0x28, 0x03, 0x52, 0x16, 0x64, + 0x65, 0x63, 0x69, 0x6d, 0x61, 0x6c, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x61, + 0x72, 0x69, 0x73, 0x6f, 0x6e, 0x12, 0x36, 0x0a, 0x17, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x61, + 0x72, 0x79, 0x5f, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, + 0x18, 0x11, 0x20, 0x01, 0x28, 0x09, 0x52, 0x15, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x61, 0x72, + 0x79, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x3c, 0x0a, + 0x1a, 0x69, 0x73, 0x73, 0x75, 0x65, 0x5f, 0x74, 0x6f, 0x6c, 0x65, 0x72, 0x61, 0x6e, 0x63, 0x65, + 0x5f, 0x70, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x61, 0x67, 0x65, 0x18, 0x12, 0x20, 0x01, 0x28, + 0x01, 0x52, 0x18, 0x69, 0x73, 0x73, 0x75, 0x65, 0x54, 0x6f, 0x6c, 0x65, 0x72, 0x61, 0x6e, 0x63, + 0x65, 0x50, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x61, 0x67, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x72, + 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x6e, 0x5f, 0x69, 0x73, 0x73, 0x75, 0x65, 0x18, + 0x13, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x4f, 0x6e, + 0x49, 0x73, 0x73, 0x75, 0x65, 0x22, 0x56, 0x0a, 0x1b, 0x49, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x53, + 0x65, 0x71, 0x75, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x4a, 0x6f, 0x62, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x37, 0x0a, 0x04, 0x6a, 0x6f, 0x62, 0x73, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x73, 0x65, + 0x72, 0x74, 0x44, 0x61, 0x74, 0x61, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x4a, 0x6f, 0x62, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x04, 0x6a, 0x6f, 0x62, 0x73, 0x22, 0x58, 0x0a, + 0x1c, 0x49, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x53, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x74, 0x69, 0x61, + 0x6c, 0x4a, 0x6f, 0x62, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x38, 0x0a, + 0x04, 0x6a, 0x6f, 0x62, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x67, 0x63, + 0x74, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x44, 0x61, 0x74, 0x61, 0x48, + 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x52, 0x04, 0x6a, 0x6f, 0x62, 0x73, 0x22, 0x4f, 0x0a, 0x1c, 0x55, 0x70, 0x73, 0x65, 0x72, + 0x74, 0x44, 0x61, 0x74, 0x61, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x4a, 0x6f, 0x62, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x12, 0x15, 0x0a, 0x06, 0x6a, 0x6f, 0x62, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x64, 0x22, 0x70, 0x0a, 0x1f, 0x47, 0x65, 0x74, 0x44, + 0x61, 0x74, 0x61, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x4a, 0x6f, 0x62, 0x44, 0x65, 0x74, + 0x61, 0x69, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x6e, + 0x69, 0x63, 0x6b, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6e, + 0x69, 0x63, 0x6b, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x66, 0x75, 0x6c, 0x6c, 0x5f, + 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x66, + 0x75, 0x6c, 0x6c, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x22, 0x87, 0x07, 0x0a, 0x0e, 0x44, + 0x61, 0x74, 0x61, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x4a, 0x6f, 0x62, 0x12, 0x0e, 0x0a, + 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1a, 0x0a, + 0x08, 0x6e, 0x69, 0x63, 0x6b, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6e, 0x69, 0x63, 0x6b, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, - 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, - 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x18, 0x03, + 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, + 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x12, 0x28, 0x0a, 0x04, 0x70, - 0x61, 0x69, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x63, 0x74, 0x72, + 0x61, 0x69, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x50, 0x61, 0x69, 0x72, 0x52, 0x04, 0x70, 0x61, 0x69, 0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x64, - 0x61, 0x74, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, + 0x61, 0x74, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x44, 0x61, 0x74, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x5f, 0x64, 0x61, 0x74, 0x65, - 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x65, 0x6e, 0x64, 0x44, 0x61, 0x74, 0x65, 0x12, - 0x1a, 0x0a, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x07, 0x20, 0x01, 0x28, + 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x65, 0x6e, 0x64, 0x44, 0x61, 0x74, 0x65, 0x12, + 0x1a, 0x0a, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x2c, 0x0a, 0x12, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x5f, 0x6c, 0x69, 0x6d, 0x69, - 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x53, 0x69, 0x7a, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x64, 0x61, 0x74, - 0x61, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x64, 0x61, - 0x74, 0x61, 0x54, 0x79, 0x70, 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x6d, 0x61, 0x78, 0x5f, 0x72, 0x65, - 0x74, 0x72, 0x79, 0x5f, 0x61, 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x73, 0x18, 0x0a, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x10, 0x6d, 0x61, 0x78, 0x52, 0x65, 0x74, 0x72, 0x79, 0x41, 0x74, 0x74, 0x65, - 0x6d, 0x70, 0x74, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x73, 0x69, - 0x7a, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x62, 0x61, 0x74, 0x63, 0x68, 0x53, - 0x69, 0x7a, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x5f, 0x6f, 0x6e, - 0x6c, 0x79, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x69, 0x6e, 0x73, 0x65, 0x72, 0x74, - 0x4f, 0x6e, 0x6c, 0x79, 0x12, 0x2f, 0x0a, 0x13, 0x63, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x73, 0x69, - 0x6f, 0x6e, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x0d, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x12, 0x63, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x74, - 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x36, 0x0a, 0x17, 0x6f, 0x76, 0x65, 0x72, 0x77, 0x72, 0x69, - 0x74, 0x65, 0x5f, 0x65, 0x78, 0x69, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x64, 0x61, 0x74, 0x61, - 0x18, 0x0e, 0x20, 0x01, 0x28, 0x08, 0x52, 0x15, 0x6f, 0x76, 0x65, 0x72, 0x77, 0x72, 0x69, 0x74, - 0x65, 0x45, 0x78, 0x69, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x44, 0x61, 0x74, 0x61, 0x12, 0x3a, 0x0a, - 0x19, 0x70, 0x72, 0x65, 0x72, 0x65, 0x71, 0x75, 0x69, 0x73, 0x69, 0x74, 0x65, 0x5f, 0x6a, 0x6f, - 0x62, 0x5f, 0x6e, 0x69, 0x63, 0x6b, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x17, 0x70, 0x72, 0x65, 0x72, 0x65, 0x71, 0x75, 0x69, 0x73, 0x69, 0x74, 0x65, 0x4a, 0x6f, - 0x62, 0x4e, 0x69, 0x63, 0x6b, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x38, 0x0a, 0x18, 0x64, 0x65, 0x63, - 0x69, 0x6d, 0x61, 0x6c, 0x5f, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x61, - 0x72, 0x69, 0x73, 0x6f, 0x6e, 0x18, 0x10, 0x20, 0x01, 0x28, 0x03, 0x52, 0x16, 0x64, 0x65, 0x63, - 0x69, 0x6d, 0x61, 0x6c, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x72, 0x69, - 0x73, 0x6f, 0x6e, 0x12, 0x36, 0x0a, 0x17, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x61, 0x72, 0x79, - 0x5f, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x11, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x15, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x45, - 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x3c, 0x0a, 0x1a, 0x69, - 0x73, 0x73, 0x75, 0x65, 0x5f, 0x74, 0x6f, 0x6c, 0x65, 0x72, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x70, - 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x61, 0x67, 0x65, 0x18, 0x12, 0x20, 0x01, 0x28, 0x01, 0x52, - 0x18, 0x69, 0x73, 0x73, 0x75, 0x65, 0x54, 0x6f, 0x6c, 0x65, 0x72, 0x61, 0x6e, 0x63, 0x65, 0x50, - 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x61, 0x67, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x72, 0x65, 0x70, - 0x6c, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x6e, 0x5f, 0x69, 0x73, 0x73, 0x75, 0x65, 0x18, 0x13, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x0e, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x4f, 0x6e, 0x49, 0x73, - 0x73, 0x75, 0x65, 0x22, 0x56, 0x0a, 0x1b, 0x49, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x53, 0x65, 0x71, - 0x75, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x4a, 0x6f, 0x62, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x37, 0x0a, 0x04, 0x6a, 0x6f, 0x62, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x23, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, - 0x44, 0x61, 0x74, 0x61, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x4a, 0x6f, 0x62, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x04, 0x6a, 0x6f, 0x62, 0x73, 0x22, 0x58, 0x0a, 0x1c, 0x49, - 0x6e, 0x73, 0x65, 0x72, 0x74, 0x53, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x4a, - 0x6f, 0x62, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x38, 0x0a, 0x04, 0x6a, - 0x6f, 0x62, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x67, 0x63, 0x74, 0x72, - 0x70, 0x63, 0x2e, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x44, 0x61, 0x74, 0x61, 0x48, 0x69, 0x73, - 0x74, 0x6f, 0x72, 0x79, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, - 0x04, 0x6a, 0x6f, 0x62, 0x73, 0x22, 0x4f, 0x0a, 0x1c, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x44, - 0x61, 0x74, 0x61, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, - 0x15, 0x0a, 0x06, 0x6a, 0x6f, 0x62, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x64, 0x22, 0x70, 0x0a, 0x1f, 0x47, 0x65, 0x74, 0x44, 0x61, 0x74, - 0x61, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x4a, 0x6f, 0x62, 0x44, 0x65, 0x74, 0x61, 0x69, - 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x6e, 0x69, 0x63, - 0x6b, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6e, 0x69, 0x63, - 0x6b, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x66, 0x75, 0x6c, 0x6c, 0x5f, 0x64, 0x65, - 0x74, 0x61, 0x69, 0x6c, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x66, 0x75, 0x6c, - 0x6c, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x22, 0x87, 0x07, 0x0a, 0x0e, 0x44, 0x61, 0x74, - 0x61, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x4a, 0x6f, 0x62, 0x12, 0x0e, 0x0a, 0x02, 0x69, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x6e, - 0x69, 0x63, 0x6b, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6e, - 0x69, 0x63, 0x6b, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, - 0x6e, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, - 0x6e, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x12, 0x28, 0x0a, 0x04, 0x70, 0x61, 0x69, - 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, - 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x50, 0x61, 0x69, 0x72, 0x52, 0x04, 0x70, - 0x61, 0x69, 0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x64, 0x61, 0x74, - 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x44, 0x61, - 0x74, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x07, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x65, 0x6e, 0x64, 0x44, 0x61, 0x74, 0x65, 0x12, 0x1a, 0x0a, - 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x2c, 0x0a, 0x12, 0x72, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, - 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x53, 0x69, - 0x7a, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x6d, 0x61, 0x78, 0x5f, 0x72, - 0x65, 0x74, 0x72, 0x79, 0x5f, 0x61, 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x73, 0x18, 0x0a, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x10, 0x6d, 0x61, 0x78, 0x52, 0x65, 0x74, 0x72, 0x79, 0x41, 0x74, 0x74, - 0x65, 0x6d, 0x70, 0x74, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x73, - 0x69, 0x7a, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x62, 0x61, 0x74, 0x63, 0x68, - 0x53, 0x69, 0x7a, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x0c, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1b, 0x0a, 0x09, - 0x64, 0x61, 0x74, 0x61, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x08, 0x64, 0x61, 0x74, 0x61, 0x54, 0x79, 0x70, 0x65, 0x12, 0x2f, 0x0a, 0x13, 0x63, 0x6f, 0x6e, - 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, - 0x18, 0x0e, 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x63, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x73, 0x69, - 0x6f, 0x6e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x36, 0x0a, 0x17, 0x6f, 0x76, - 0x65, 0x72, 0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, 0x65, 0x78, 0x69, 0x73, 0x74, 0x69, 0x6e, 0x67, - 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x08, 0x52, 0x15, 0x6f, 0x76, 0x65, - 0x72, 0x77, 0x72, 0x69, 0x74, 0x65, 0x45, 0x78, 0x69, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x44, 0x61, - 0x74, 0x61, 0x12, 0x3a, 0x0a, 0x19, 0x70, 0x72, 0x65, 0x72, 0x65, 0x71, 0x75, 0x69, 0x73, 0x69, - 0x74, 0x65, 0x5f, 0x6a, 0x6f, 0x62, 0x5f, 0x6e, 0x69, 0x63, 0x6b, 0x6e, 0x61, 0x6d, 0x65, 0x18, - 0x10, 0x20, 0x01, 0x28, 0x09, 0x52, 0x17, 0x70, 0x72, 0x65, 0x72, 0x65, 0x71, 0x75, 0x69, 0x73, - 0x69, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x4e, 0x69, 0x63, 0x6b, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x38, - 0x0a, 0x18, 0x64, 0x65, 0x63, 0x69, 0x6d, 0x61, 0x6c, 0x5f, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x5f, - 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x72, 0x69, 0x73, 0x6f, 0x6e, 0x18, 0x11, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x16, 0x64, 0x65, 0x63, 0x69, 0x6d, 0x61, 0x6c, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x43, 0x6f, - 0x6d, 0x70, 0x61, 0x72, 0x69, 0x73, 0x6f, 0x6e, 0x12, 0x36, 0x0a, 0x17, 0x73, 0x65, 0x63, 0x6f, - 0x6e, 0x64, 0x61, 0x72, 0x79, 0x5f, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x5f, 0x6e, - 0x61, 0x6d, 0x65, 0x18, 0x12, 0x20, 0x01, 0x28, 0x09, 0x52, 0x15, 0x73, 0x65, 0x63, 0x6f, 0x6e, - 0x64, 0x61, 0x72, 0x79, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4e, 0x61, 0x6d, 0x65, - 0x12, 0x3c, 0x0a, 0x1a, 0x69, 0x73, 0x73, 0x75, 0x65, 0x5f, 0x74, 0x6f, 0x6c, 0x65, 0x72, 0x61, - 0x6e, 0x63, 0x65, 0x5f, 0x70, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x61, 0x67, 0x65, 0x18, 0x13, - 0x20, 0x01, 0x28, 0x01, 0x52, 0x18, 0x69, 0x73, 0x73, 0x75, 0x65, 0x54, 0x6f, 0x6c, 0x65, 0x72, - 0x61, 0x6e, 0x63, 0x65, 0x50, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x61, 0x67, 0x65, 0x12, 0x28, - 0x0a, 0x10, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x6e, 0x5f, 0x69, 0x73, 0x73, - 0x75, 0x65, 0x18, 0x14, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, - 0x65, 0x4f, 0x6e, 0x49, 0x73, 0x73, 0x75, 0x65, 0x12, 0x3d, 0x0a, 0x0b, 0x6a, 0x6f, 0x62, 0x5f, - 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x15, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, - 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x48, 0x69, 0x73, 0x74, 0x6f, - 0x72, 0x79, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x0a, 0x6a, 0x6f, 0x62, - 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x73, 0x75, 0x6c, - 0x74, 0x5f, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x69, 0x65, 0x73, 0x18, 0x16, 0x20, 0x03, 0x28, - 0x09, 0x52, 0x0f, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x69, - 0x65, 0x73, 0x22, 0xa0, 0x01, 0x0a, 0x14, 0x44, 0x61, 0x74, 0x61, 0x48, 0x69, 0x73, 0x74, 0x6f, - 0x72, 0x79, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x73, - 0x74, 0x61, 0x72, 0x74, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x44, 0x61, 0x74, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x65, 0x6e, - 0x64, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x65, 0x6e, - 0x64, 0x44, 0x61, 0x74, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x68, 0x61, 0x73, 0x5f, 0x64, 0x61, 0x74, - 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x68, 0x61, 0x73, 0x44, 0x61, 0x74, 0x61, - 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x72, 0x75, - 0x6e, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x72, 0x75, - 0x6e, 0x44, 0x61, 0x74, 0x65, 0x22, 0x43, 0x0a, 0x0f, 0x44, 0x61, 0x74, 0x61, 0x48, 0x69, 0x73, - 0x74, 0x6f, 0x72, 0x79, 0x4a, 0x6f, 0x62, 0x73, 0x12, 0x30, 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75, - 0x6c, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x67, 0x63, 0x74, 0x72, - 0x70, 0x63, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x4a, 0x6f, - 0x62, 0x52, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x22, 0x5c, 0x0a, 0x20, 0x47, 0x65, - 0x74, 0x44, 0x61, 0x74, 0x61, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x4a, 0x6f, 0x62, 0x73, - 0x42, 0x65, 0x74, 0x77, 0x65, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, - 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x44, 0x61, 0x74, 0x65, 0x12, 0x19, 0x0a, - 0x08, 0x65, 0x6e, 0x64, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x07, 0x65, 0x6e, 0x64, 0x44, 0x61, 0x74, 0x65, 0x22, 0x64, 0x0a, 0x1e, 0x53, 0x65, 0x74, 0x44, - 0x61, 0x74, 0x61, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x4a, 0x6f, 0x62, 0x53, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x6e, 0x69, - 0x63, 0x6b, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6e, 0x69, - 0x63, 0x6b, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x81, - 0x01, 0x0a, 0x27, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x44, 0x61, 0x74, 0x61, 0x48, 0x69, 0x73, - 0x74, 0x6f, 0x72, 0x79, 0x4a, 0x6f, 0x62, 0x50, 0x72, 0x65, 0x72, 0x65, 0x71, 0x75, 0x69, 0x73, - 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x6e, 0x69, - 0x63, 0x6b, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6e, 0x69, - 0x63, 0x6b, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x3a, 0x0a, 0x19, 0x70, 0x72, 0x65, 0x72, 0x65, 0x71, - 0x75, 0x69, 0x73, 0x69, 0x74, 0x65, 0x5f, 0x6a, 0x6f, 0x62, 0x5f, 0x6e, 0x69, 0x63, 0x6b, 0x6e, - 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x17, 0x70, 0x72, 0x65, 0x72, 0x65, - 0x71, 0x75, 0x69, 0x73, 0x69, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x4e, 0x69, 0x63, 0x6b, 0x6e, 0x61, - 0x6d, 0x65, 0x22, 0xb9, 0x01, 0x0a, 0x12, 0x4d, 0x6f, 0x64, 0x69, 0x66, 0x79, 0x4f, 0x72, 0x64, - 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, - 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, - 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x69, - 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x64, - 0x12, 0x28, 0x0a, 0x04, 0x70, 0x61, 0x69, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, - 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, - 0x50, 0x61, 0x69, 0x72, 0x52, 0x04, 0x70, 0x61, 0x69, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x73, - 0x73, 0x65, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, - 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x01, - 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x70, 0x72, 0x69, 0x63, - 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x01, 0x52, 0x05, 0x70, 0x72, 0x69, 0x63, 0x65, 0x22, 0x41, - 0x0a, 0x13, 0x4d, 0x6f, 0x64, 0x69, 0x66, 0x79, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, - 0x64, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0f, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, 0x64, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x49, - 0x64, 0x22, 0x38, 0x0a, 0x1a, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x53, 0x74, 0x61, - 0x74, 0x65, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x22, 0x63, 0x0a, 0x1b, 0x43, - 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x53, 0x74, 0x61, 0x74, 0x65, 0x54, 0x72, 0x61, 0x64, - 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, - 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, - 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x73, - 0x73, 0x65, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, - 0x22, 0x67, 0x0a, 0x1f, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x53, 0x74, 0x61, 0x74, - 0x65, 0x54, 0x72, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x50, 0x61, 0x69, 0x72, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, - 0x12, 0x0a, 0x04, 0x70, 0x61, 0x69, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, - 0x61, 0x69, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x22, 0x64, 0x0a, 0x1c, 0x43, 0x75, 0x72, - 0x72, 0x65, 0x6e, 0x63, 0x79, 0x53, 0x74, 0x61, 0x74, 0x65, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, - 0x61, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, - 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, - 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x73, 0x73, - 0x65, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x22, - 0x63, 0x0a, 0x1b, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x53, 0x74, 0x61, 0x74, 0x65, - 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, - 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, - 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x14, - 0x0a, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, - 0x73, 0x73, 0x65, 0x74, 0x22, 0x57, 0x0a, 0x15, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, - 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, - 0x0f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x73, - 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, - 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x0e, 0x63, - 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x53, 0x74, 0x61, 0x74, 0x65, 0x73, 0x22, 0xbe, 0x01, - 0x0a, 0x0d, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, - 0x1a, 0x0a, 0x08, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x08, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x61, - 0x73, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x73, 0x73, 0x65, - 0x74, 0x12, 0x29, 0x0a, 0x10, 0x77, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x5f, 0x65, 0x6e, - 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x77, 0x69, 0x74, - 0x68, 0x64, 0x72, 0x61, 0x77, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x27, 0x0a, 0x0f, - 0x64, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x64, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x45, 0x6e, - 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x27, 0x0a, 0x0f, 0x74, 0x72, 0x61, 0x64, 0x69, 0x6e, 0x67, - 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, - 0x74, 0x72, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x22, 0x4f, - 0x0a, 0x0b, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x61, 0x74, 0x65, 0x12, 0x12, 0x0a, - 0x04, 0x64, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x64, 0x61, 0x74, - 0x65, 0x12, 0x12, 0x0a, 0x04, 0x72, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x04, 0x72, 0x61, 0x74, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x22, - 0xde, 0x03, 0x0a, 0x0b, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x44, 0x61, 0x74, 0x61, 0x12, - 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x61, - 0x73, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x73, 0x73, 0x65, - 0x74, 0x12, 0x28, 0x0a, 0x04, 0x70, 0x61, 0x69, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x14, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, - 0x79, 0x50, 0x61, 0x69, 0x72, 0x52, 0x04, 0x70, 0x61, 0x69, 0x72, 0x12, 0x29, 0x0a, 0x10, 0x70, - 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x43, 0x75, - 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, - 0x64, 0x61, 0x74, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, - 0x74, 0x44, 0x61, 0x74, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x5f, 0x64, 0x61, 0x74, - 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x65, 0x6e, 0x64, 0x44, 0x61, 0x74, 0x65, - 0x12, 0x29, 0x0a, 0x05, 0x72, 0x61, 0x74, 0x65, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x13, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, - 0x52, 0x61, 0x74, 0x65, 0x52, 0x05, 0x72, 0x61, 0x74, 0x65, 0x73, 0x12, 0x34, 0x0a, 0x0b, 0x6c, - 0x61, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x13, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, - 0x67, 0x52, 0x61, 0x74, 0x65, 0x52, 0x0a, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x52, 0x61, 0x74, - 0x65, 0x12, 0x38, 0x0a, 0x0d, 0x75, 0x70, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x5f, 0x72, 0x61, - 0x74, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, - 0x63, 0x2e, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x61, 0x74, 0x65, 0x52, 0x0c, 0x75, - 0x70, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x52, 0x61, 0x74, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x70, - 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x75, 0x6d, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0a, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x75, 0x6d, 0x12, 0x27, 0x0a, 0x0f, - 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, - 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x4d, 0x65, - 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x29, 0x0a, 0x11, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6f, 0x66, - 0x5f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0e, 0x74, 0x69, 0x6d, 0x65, 0x4f, 0x66, 0x4e, 0x65, 0x78, 0x74, 0x52, 0x61, 0x74, 0x65, - 0x22, 0xac, 0x09, 0x0a, 0x14, 0x46, 0x75, 0x74, 0x75, 0x72, 0x65, 0x73, 0x50, 0x6f, 0x73, 0x69, - 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x44, 0x0a, 0x1e, 0x6d, 0x61, 0x69, - 0x6e, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x5f, - 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x1c, 0x6d, 0x61, 0x69, 0x6e, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x63, 0x65, 0x4d, 0x61, + 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x53, 0x69, 0x7a, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x6d, 0x61, 0x78, + 0x5f, 0x72, 0x65, 0x74, 0x72, 0x79, 0x5f, 0x61, 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x73, 0x18, + 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x6d, 0x61, 0x78, 0x52, 0x65, 0x74, 0x72, 0x79, 0x41, + 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x61, 0x74, 0x63, 0x68, + 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x62, 0x61, 0x74, + 0x63, 0x68, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1b, + 0x0a, 0x09, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x0d, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x08, 0x64, 0x61, 0x74, 0x61, 0x54, 0x79, 0x70, 0x65, 0x12, 0x2f, 0x0a, 0x13, 0x63, + 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, + 0x61, 0x6c, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x63, 0x6f, 0x6e, 0x76, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x36, 0x0a, 0x17, + 0x6f, 0x76, 0x65, 0x72, 0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, 0x65, 0x78, 0x69, 0x73, 0x74, 0x69, + 0x6e, 0x67, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x08, 0x52, 0x15, 0x6f, + 0x76, 0x65, 0x72, 0x77, 0x72, 0x69, 0x74, 0x65, 0x45, 0x78, 0x69, 0x73, 0x74, 0x69, 0x6e, 0x67, + 0x44, 0x61, 0x74, 0x61, 0x12, 0x3a, 0x0a, 0x19, 0x70, 0x72, 0x65, 0x72, 0x65, 0x71, 0x75, 0x69, + 0x73, 0x69, 0x74, 0x65, 0x5f, 0x6a, 0x6f, 0x62, 0x5f, 0x6e, 0x69, 0x63, 0x6b, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x10, 0x20, 0x01, 0x28, 0x09, 0x52, 0x17, 0x70, 0x72, 0x65, 0x72, 0x65, 0x71, 0x75, + 0x69, 0x73, 0x69, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x4e, 0x69, 0x63, 0x6b, 0x6e, 0x61, 0x6d, 0x65, + 0x12, 0x38, 0x0a, 0x18, 0x64, 0x65, 0x63, 0x69, 0x6d, 0x61, 0x6c, 0x5f, 0x70, 0x6c, 0x61, 0x63, + 0x65, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x72, 0x69, 0x73, 0x6f, 0x6e, 0x18, 0x11, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x16, 0x64, 0x65, 0x63, 0x69, 0x6d, 0x61, 0x6c, 0x50, 0x6c, 0x61, 0x63, 0x65, + 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x72, 0x69, 0x73, 0x6f, 0x6e, 0x12, 0x36, 0x0a, 0x17, 0x73, 0x65, + 0x63, 0x6f, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x5f, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, + 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x12, 0x20, 0x01, 0x28, 0x09, 0x52, 0x15, 0x73, 0x65, 0x63, + 0x6f, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4e, 0x61, + 0x6d, 0x65, 0x12, 0x3c, 0x0a, 0x1a, 0x69, 0x73, 0x73, 0x75, 0x65, 0x5f, 0x74, 0x6f, 0x6c, 0x65, + 0x72, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x70, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x61, 0x67, 0x65, + 0x18, 0x13, 0x20, 0x01, 0x28, 0x01, 0x52, 0x18, 0x69, 0x73, 0x73, 0x75, 0x65, 0x54, 0x6f, 0x6c, + 0x65, 0x72, 0x61, 0x6e, 0x63, 0x65, 0x50, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x61, 0x67, 0x65, + 0x12, 0x28, 0x0a, 0x10, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x6e, 0x5f, 0x69, + 0x73, 0x73, 0x75, 0x65, 0x18, 0x14, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x72, 0x65, 0x70, 0x6c, + 0x61, 0x63, 0x65, 0x4f, 0x6e, 0x49, 0x73, 0x73, 0x75, 0x65, 0x12, 0x3d, 0x0a, 0x0b, 0x6a, 0x6f, + 0x62, 0x5f, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x15, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x1c, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x48, 0x69, 0x73, + 0x74, 0x6f, 0x72, 0x79, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x0a, 0x6a, + 0x6f, 0x62, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x73, + 0x75, 0x6c, 0x74, 0x5f, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x69, 0x65, 0x73, 0x18, 0x16, 0x20, + 0x03, 0x28, 0x09, 0x52, 0x0f, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x53, 0x75, 0x6d, 0x6d, 0x61, + 0x72, 0x69, 0x65, 0x73, 0x22, 0xa0, 0x01, 0x0a, 0x14, 0x44, 0x61, 0x74, 0x61, 0x48, 0x69, 0x73, + 0x74, 0x6f, 0x72, 0x79, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x1d, 0x0a, + 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x44, 0x61, 0x74, 0x65, 0x12, 0x19, 0x0a, 0x08, + 0x65, 0x6e, 0x64, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x65, 0x6e, 0x64, 0x44, 0x61, 0x74, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x68, 0x61, 0x73, 0x5f, 0x64, + 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x68, 0x61, 0x73, 0x44, 0x61, + 0x74, 0x61, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x19, 0x0a, 0x08, + 0x72, 0x75, 0x6e, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x72, 0x75, 0x6e, 0x44, 0x61, 0x74, 0x65, 0x22, 0x43, 0x0a, 0x0f, 0x44, 0x61, 0x74, 0x61, 0x48, + 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x4a, 0x6f, 0x62, 0x73, 0x12, 0x30, 0x0a, 0x07, 0x72, 0x65, + 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x67, 0x63, + 0x74, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, + 0x4a, 0x6f, 0x62, 0x52, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x22, 0x5c, 0x0a, 0x20, + 0x47, 0x65, 0x74, 0x44, 0x61, 0x74, 0x61, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x4a, 0x6f, + 0x62, 0x73, 0x42, 0x65, 0x74, 0x77, 0x65, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x44, 0x61, 0x74, 0x65, 0x12, + 0x19, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x07, 0x65, 0x6e, 0x64, 0x44, 0x61, 0x74, 0x65, 0x22, 0x64, 0x0a, 0x1e, 0x53, 0x65, + 0x74, 0x44, 0x61, 0x74, 0x61, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x4a, 0x6f, 0x62, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1a, 0x0a, 0x08, + 0x6e, 0x69, 0x63, 0x6b, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, + 0x6e, 0x69, 0x63, 0x6b, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x22, 0x81, 0x01, 0x0a, 0x27, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x44, 0x61, 0x74, 0x61, 0x48, + 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x4a, 0x6f, 0x62, 0x50, 0x72, 0x65, 0x72, 0x65, 0x71, 0x75, + 0x69, 0x73, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, + 0x6e, 0x69, 0x63, 0x6b, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, + 0x6e, 0x69, 0x63, 0x6b, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x3a, 0x0a, 0x19, 0x70, 0x72, 0x65, 0x72, + 0x65, 0x71, 0x75, 0x69, 0x73, 0x69, 0x74, 0x65, 0x5f, 0x6a, 0x6f, 0x62, 0x5f, 0x6e, 0x69, 0x63, + 0x6b, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x17, 0x70, 0x72, 0x65, + 0x72, 0x65, 0x71, 0x75, 0x69, 0x73, 0x69, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x4e, 0x69, 0x63, 0x6b, + 0x6e, 0x61, 0x6d, 0x65, 0x22, 0xb9, 0x01, 0x0a, 0x12, 0x4d, 0x6f, 0x64, 0x69, 0x66, 0x79, 0x4f, + 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x65, + 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, + 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x6f, 0x72, 0x64, 0x65, 0x72, + 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6f, 0x72, 0x64, 0x65, 0x72, + 0x49, 0x64, 0x12, 0x28, 0x0a, 0x04, 0x70, 0x61, 0x69, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x14, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, + 0x63, 0x79, 0x50, 0x61, 0x69, 0x72, 0x52, 0x04, 0x70, 0x61, 0x69, 0x72, 0x12, 0x14, 0x0a, 0x05, + 0x61, 0x73, 0x73, 0x65, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x73, 0x73, + 0x65, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x01, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x70, 0x72, + 0x69, 0x63, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x01, 0x52, 0x05, 0x70, 0x72, 0x69, 0x63, 0x65, + 0x22, 0x41, 0x0a, 0x13, 0x4d, 0x6f, 0x64, 0x69, 0x66, 0x79, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x6d, 0x6f, 0x64, 0x69, 0x66, + 0x69, 0x65, 0x64, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0f, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, 0x64, 0x4f, 0x72, 0x64, 0x65, + 0x72, 0x49, 0x64, 0x22, 0x38, 0x0a, 0x1a, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x53, + 0x74, 0x61, 0x74, 0x65, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x22, 0x63, 0x0a, + 0x1b, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x53, 0x74, 0x61, 0x74, 0x65, 0x54, 0x72, + 0x61, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, + 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, + 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x14, 0x0a, 0x05, + 0x61, 0x73, 0x73, 0x65, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x73, 0x73, + 0x65, 0x74, 0x22, 0x67, 0x0a, 0x1f, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x53, 0x74, + 0x61, 0x74, 0x65, 0x54, 0x72, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x50, 0x61, 0x69, 0x72, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, + 0x65, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x69, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x70, 0x61, 0x69, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x22, 0x64, 0x0a, 0x1c, 0x43, + 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x53, 0x74, 0x61, 0x74, 0x65, 0x57, 0x69, 0x74, 0x68, + 0x64, 0x72, 0x61, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x65, + 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, + 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x61, + 0x73, 0x73, 0x65, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x73, 0x73, 0x65, + 0x74, 0x22, 0x63, 0x0a, 0x1b, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x53, 0x74, 0x61, + 0x74, 0x65, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, + 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, + 0x12, 0x14, 0x0a, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x22, 0x57, 0x0a, 0x15, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, + 0x63, 0x79, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x3e, 0x0a, 0x0f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x73, 0x74, 0x61, 0x74, + 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, + 0x63, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, + 0x0e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x53, 0x74, 0x61, 0x74, 0x65, 0x73, 0x22, + 0xbe, 0x01, 0x0a, 0x0d, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x53, 0x74, 0x61, 0x74, + 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x14, 0x0a, + 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x73, + 0x73, 0x65, 0x74, 0x12, 0x29, 0x0a, 0x10, 0x77, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x5f, + 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x77, + 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x27, + 0x0a, 0x0f, 0x64, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, + 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x64, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, + 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x27, 0x0a, 0x0f, 0x74, 0x72, 0x61, 0x64, 0x69, + 0x6e, 0x67, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x0e, 0x74, 0x72, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, + 0x22, 0x4f, 0x0a, 0x0b, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x61, 0x74, 0x65, 0x12, + 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x64, + 0x61, 0x74, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x72, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x72, 0x61, 0x74, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6d, 0x65, + 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, + 0x74, 0x22, 0xde, 0x03, 0x0a, 0x0b, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x44, 0x61, 0x74, + 0x61, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x14, 0x0a, + 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x73, + 0x73, 0x65, 0x74, 0x12, 0x28, 0x0a, 0x04, 0x70, 0x61, 0x69, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, + 0x6e, 0x63, 0x79, 0x50, 0x61, 0x69, 0x72, 0x52, 0x04, 0x70, 0x61, 0x69, 0x72, 0x12, 0x29, 0x0a, + 0x10, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, + 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, + 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, + 0x74, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x74, + 0x61, 0x72, 0x74, 0x44, 0x61, 0x74, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x5f, 0x64, + 0x61, 0x74, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x65, 0x6e, 0x64, 0x44, 0x61, + 0x74, 0x65, 0x12, 0x29, 0x0a, 0x05, 0x72, 0x61, 0x74, 0x65, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x13, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, 0x6e, 0x64, 0x69, + 0x6e, 0x67, 0x52, 0x61, 0x74, 0x65, 0x52, 0x05, 0x72, 0x61, 0x74, 0x65, 0x73, 0x12, 0x34, 0x0a, + 0x0b, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, 0x08, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, 0x6e, 0x64, + 0x69, 0x6e, 0x67, 0x52, 0x61, 0x74, 0x65, 0x52, 0x0a, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x52, + 0x61, 0x74, 0x65, 0x12, 0x38, 0x0a, 0x0d, 0x75, 0x70, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x5f, + 0x72, 0x61, 0x74, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x67, 0x63, 0x74, + 0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x61, 0x74, 0x65, 0x52, + 0x0c, 0x75, 0x70, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x52, 0x61, 0x74, 0x65, 0x12, 0x1f, 0x0a, + 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x75, 0x6d, 0x18, 0x0a, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0a, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x75, 0x6d, 0x12, 0x27, + 0x0a, 0x0f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, + 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x29, 0x0a, 0x11, 0x74, 0x69, 0x6d, 0x65, 0x5f, + 0x6f, 0x66, 0x5f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, 0x0c, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0e, 0x74, 0x69, 0x6d, 0x65, 0x4f, 0x66, 0x4e, 0x65, 0x78, 0x74, 0x52, 0x61, + 0x74, 0x65, 0x22, 0xac, 0x09, 0x0a, 0x14, 0x46, 0x75, 0x74, 0x75, 0x72, 0x65, 0x73, 0x50, 0x6f, + 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x44, 0x0a, 0x1e, 0x6d, + 0x61, 0x69, 0x6e, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x6d, 0x61, 0x72, 0x67, 0x69, + 0x6e, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x1c, 0x6d, 0x61, 0x69, 0x6e, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x63, 0x65, + 0x4d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x6d, 0x65, 0x6e, + 0x74, 0x12, 0x3c, 0x0a, 0x1a, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x6d, 0x61, 0x72, + 0x67, 0x69, 0x6e, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x18, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x4d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x12, - 0x3c, 0x0a, 0x1a, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x6d, 0x61, 0x72, 0x67, 0x69, - 0x6e, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x18, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x4d, 0x61, 0x72, 0x67, - 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x3e, 0x0a, - 0x1b, 0x65, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x6c, 0x69, 0x71, 0x75, 0x69, - 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x19, 0x65, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x64, 0x4c, 0x69, 0x71, - 0x75, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, 0x27, 0x0a, - 0x0f, 0x63, 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, 0x5f, 0x75, 0x73, 0x65, 0x64, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x63, 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, 0x72, - 0x61, 0x6c, 0x55, 0x73, 0x65, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x6d, 0x61, 0x72, 0x6b, 0x5f, 0x70, - 0x72, 0x69, 0x63, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6d, 0x61, 0x72, 0x6b, - 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, - 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x75, 0x72, - 0x72, 0x65, 0x6e, 0x74, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6f, 0x6e, 0x74, - 0x72, 0x61, 0x63, 0x74, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x2f, 0x0a, - 0x13, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x5f, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, - 0x6c, 0x69, 0x65, 0x72, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x63, 0x6f, 0x6e, 0x74, - 0x72, 0x61, 0x63, 0x74, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x69, 0x65, 0x72, 0x12, 0x38, - 0x0a, 0x18, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x6c, - 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x16, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x53, 0x65, 0x74, 0x74, 0x6c, 0x65, - 0x6d, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x62, 0x72, 0x65, 0x61, - 0x6b, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x0a, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0e, 0x62, 0x72, 0x65, 0x61, 0x6b, 0x45, 0x76, 0x65, 0x6e, 0x50, 0x72, 0x69, - 0x63, 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x61, 0x76, 0x65, 0x72, 0x61, 0x67, 0x65, 0x5f, 0x6f, 0x70, - 0x65, 0x6e, 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, - 0x61, 0x76, 0x65, 0x72, 0x61, 0x67, 0x65, 0x4f, 0x70, 0x65, 0x6e, 0x50, 0x72, 0x69, 0x63, 0x65, - 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x63, 0x65, 0x6e, 0x74, 0x5f, 0x70, 0x6e, 0x6c, 0x18, 0x0c, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x63, 0x65, 0x6e, 0x74, 0x50, 0x6e, 0x6c, 0x12, - 0x27, 0x0a, 0x0f, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x5f, 0x66, 0x72, 0x61, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, - 0x46, 0x72, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x27, 0x0a, 0x0f, 0x66, 0x72, 0x65, 0x65, - 0x5f, 0x63, 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, 0x18, 0x0e, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0e, 0x66, 0x72, 0x65, 0x65, 0x43, 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, - 0x6c, 0x12, 0x29, 0x0a, 0x10, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x63, 0x6f, 0x6c, 0x6c, 0x61, - 0x74, 0x65, 0x72, 0x61, 0x6c, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x74, 0x6f, 0x74, - 0x61, 0x6c, 0x43, 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, 0x12, 0x25, 0x0a, 0x0e, - 0x66, 0x72, 0x6f, 0x7a, 0x65, 0x6e, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x10, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x66, 0x72, 0x6f, 0x7a, 0x65, 0x6e, 0x42, 0x61, 0x6c, 0x61, - 0x6e, 0x63, 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x65, 0x71, 0x75, 0x69, 0x74, 0x79, 0x5f, 0x6f, 0x66, - 0x5f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x11, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x10, 0x65, 0x71, 0x75, 0x69, 0x74, 0x79, 0x4f, 0x66, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, - 0x79, 0x12, 0x29, 0x0a, 0x10, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x65, - 0x71, 0x75, 0x69, 0x74, 0x79, 0x18, 0x12, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x61, 0x76, 0x61, - 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x45, 0x71, 0x75, 0x69, 0x74, 0x79, 0x12, 0x21, 0x0a, 0x0c, - 0x63, 0x61, 0x73, 0x68, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x13, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0b, 0x63, 0x61, 0x73, 0x68, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, - 0x27, 0x0a, 0x0f, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x65, 0x71, 0x75, 0x69, - 0x74, 0x79, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x75, - 0x6e, 0x74, 0x45, 0x71, 0x75, 0x69, 0x74, 0x79, 0x12, 0x1d, 0x0a, 0x0a, 0x65, 0x71, 0x75, 0x69, - 0x74, 0x79, 0x5f, 0x75, 0x73, 0x64, 0x18, 0x15, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x65, 0x71, - 0x75, 0x69, 0x74, 0x79, 0x55, 0x73, 0x64, 0x12, 0x27, 0x0a, 0x0f, 0x69, 0x73, 0x6f, 0x6c, 0x61, - 0x74, 0x65, 0x64, 0x5f, 0x65, 0x71, 0x75, 0x69, 0x74, 0x79, 0x18, 0x16, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0e, 0x69, 0x73, 0x6f, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x45, 0x71, 0x75, 0x69, 0x74, 0x79, - 0x12, 0x31, 0x0a, 0x14, 0x69, 0x73, 0x6f, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x6c, 0x69, 0x61, - 0x62, 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x18, 0x17, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, - 0x69, 0x73, 0x6f, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x4c, 0x69, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, - 0x69, 0x65, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x69, 0x73, 0x6f, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x5f, - 0x75, 0x70, 0x6c, 0x18, 0x18, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x69, 0x73, 0x6f, 0x6c, 0x61, - 0x74, 0x65, 0x64, 0x55, 0x70, 0x6c, 0x12, 0x2b, 0x0a, 0x11, 0x6e, 0x6f, 0x74, 0x69, 0x6f, 0x6e, - 0x61, 0x6c, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x72, 0x61, 0x67, 0x65, 0x18, 0x19, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x10, 0x6e, 0x6f, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x4c, 0x65, 0x76, 0x65, 0x72, - 0x61, 0x67, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x65, 0x71, 0x75, - 0x69, 0x74, 0x79, 0x18, 0x1a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x74, 0x6f, 0x74, 0x61, 0x6c, - 0x45, 0x71, 0x75, 0x69, 0x74, 0x79, 0x12, 0x27, 0x0a, 0x0f, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, - 0x67, 0x79, 0x5f, 0x65, 0x71, 0x75, 0x69, 0x74, 0x79, 0x18, 0x1b, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0e, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x45, 0x71, 0x75, 0x69, 0x74, 0x79, 0x22, - 0x84, 0x06, 0x0a, 0x0e, 0x46, 0x75, 0x74, 0x75, 0x72, 0x65, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, - 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x14, - 0x0a, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, - 0x73, 0x73, 0x65, 0x74, 0x12, 0x28, 0x0a, 0x04, 0x70, 0x61, 0x69, 0x72, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x75, 0x72, 0x72, - 0x65, 0x6e, 0x63, 0x79, 0x50, 0x61, 0x69, 0x72, 0x52, 0x04, 0x70, 0x61, 0x69, 0x72, 0x12, 0x16, - 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, - 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x6f, 0x70, 0x65, 0x6e, 0x69, 0x6e, - 0x67, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6f, 0x70, - 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x44, 0x61, 0x74, 0x65, 0x12, 0x2b, 0x0a, 0x11, 0x6f, 0x70, 0x65, - 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x6f, 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x44, 0x69, 0x72, - 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x6f, 0x70, 0x65, 0x6e, 0x69, 0x6e, - 0x67, 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6f, - 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x6f, - 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0b, 0x6f, 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x2b, - 0x0a, 0x11, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x63, 0x75, 0x72, 0x72, 0x65, - 0x6e, 0x74, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x63, - 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x0a, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0c, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x50, 0x72, 0x69, 0x63, 0x65, - 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x69, 0x7a, 0x65, - 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x53, - 0x69, 0x7a, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x75, 0x6e, 0x72, 0x65, 0x61, 0x6c, 0x69, 0x73, 0x65, - 0x64, 0x5f, 0x70, 0x6e, 0x6c, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x75, 0x6e, 0x72, - 0x65, 0x61, 0x6c, 0x69, 0x73, 0x65, 0x64, 0x50, 0x6e, 0x6c, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x65, - 0x61, 0x6c, 0x69, 0x73, 0x65, 0x64, 0x5f, 0x70, 0x6e, 0x6c, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0b, 0x72, 0x65, 0x61, 0x6c, 0x69, 0x73, 0x65, 0x64, 0x50, 0x6e, 0x6c, 0x12, 0x21, 0x0a, - 0x0c, 0x63, 0x6c, 0x6f, 0x73, 0x69, 0x6e, 0x67, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x0e, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6c, 0x6f, 0x73, 0x69, 0x6e, 0x67, 0x44, 0x61, 0x74, 0x65, - 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, - 0x0f, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x75, 0x6e, - 0x74, 0x12, 0x38, 0x0a, 0x18, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x5f, 0x73, 0x65, - 0x74, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x10, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x16, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x53, 0x65, 0x74, - 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x2c, 0x0a, 0x06, 0x6f, - 0x72, 0x64, 0x65, 0x72, 0x73, 0x18, 0x11, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x63, - 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, - 0x73, 0x52, 0x06, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x12, 0x43, 0x0a, 0x0e, 0x70, 0x6f, 0x73, - 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x73, 0x18, 0x12, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, 0x74, 0x75, 0x72, - 0x65, 0x73, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, - 0x0d, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x36, - 0x0a, 0x0c, 0x66, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x13, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, - 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x44, 0x61, 0x74, 0x61, 0x52, 0x0b, 0x66, 0x75, 0x6e, 0x64, 0x69, - 0x6e, 0x67, 0x44, 0x61, 0x74, 0x61, 0x22, 0xd3, 0x02, 0x0a, 0x19, 0x47, 0x65, 0x74, 0x4d, 0x61, - 0x6e, 0x61, 0x67, 0x65, 0x64, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, + 0x3e, 0x0a, 0x1b, 0x65, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x6c, 0x69, 0x71, + 0x75, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x19, 0x65, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x64, 0x4c, + 0x69, 0x71, 0x75, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, + 0x27, 0x0a, 0x0f, 0x63, 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, 0x5f, 0x75, 0x73, + 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x63, 0x6f, 0x6c, 0x6c, 0x61, 0x74, + 0x65, 0x72, 0x61, 0x6c, 0x55, 0x73, 0x65, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x6d, 0x61, 0x72, 0x6b, + 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6d, 0x61, + 0x72, 0x6b, 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x75, 0x72, 0x72, 0x65, + 0x6e, 0x74, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, + 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6f, + 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x53, 0x69, 0x7a, 0x65, 0x12, + 0x2f, 0x0a, 0x13, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x5f, 0x6d, 0x75, 0x6c, 0x74, + 0x69, 0x70, 0x6c, 0x69, 0x65, 0x72, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x63, 0x6f, + 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x69, 0x65, 0x72, + 0x12, 0x38, 0x0a, 0x18, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x5f, 0x73, 0x65, 0x74, + 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x09, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x16, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x53, 0x65, 0x74, 0x74, + 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x62, 0x72, + 0x65, 0x61, 0x6b, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x0a, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x62, 0x72, 0x65, 0x61, 0x6b, 0x45, 0x76, 0x65, 0x6e, 0x50, + 0x72, 0x69, 0x63, 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x61, 0x76, 0x65, 0x72, 0x61, 0x67, 0x65, 0x5f, + 0x6f, 0x70, 0x65, 0x6e, 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x10, 0x61, 0x76, 0x65, 0x72, 0x61, 0x67, 0x65, 0x4f, 0x70, 0x65, 0x6e, 0x50, 0x72, 0x69, + 0x63, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x63, 0x65, 0x6e, 0x74, 0x5f, 0x70, 0x6e, 0x6c, + 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x63, 0x65, 0x6e, 0x74, 0x50, 0x6e, + 0x6c, 0x12, 0x27, 0x0a, 0x0f, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x5f, 0x66, 0x72, 0x61, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6d, 0x61, 0x72, 0x67, + 0x69, 0x6e, 0x46, 0x72, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x27, 0x0a, 0x0f, 0x66, 0x72, + 0x65, 0x65, 0x5f, 0x63, 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, 0x18, 0x0e, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0e, 0x66, 0x72, 0x65, 0x65, 0x43, 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, + 0x72, 0x61, 0x6c, 0x12, 0x29, 0x0a, 0x10, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x63, 0x6f, 0x6c, + 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x74, + 0x6f, 0x74, 0x61, 0x6c, 0x43, 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, 0x12, 0x25, + 0x0a, 0x0e, 0x66, 0x72, 0x6f, 0x7a, 0x65, 0x6e, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, + 0x18, 0x10, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x66, 0x72, 0x6f, 0x7a, 0x65, 0x6e, 0x42, 0x61, + 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x65, 0x71, 0x75, 0x69, 0x74, 0x79, 0x5f, + 0x6f, 0x66, 0x5f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x11, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x10, 0x65, 0x71, 0x75, 0x69, 0x74, 0x79, 0x4f, 0x66, 0x43, 0x75, 0x72, 0x72, 0x65, + 0x6e, 0x63, 0x79, 0x12, 0x29, 0x0a, 0x10, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, + 0x5f, 0x65, 0x71, 0x75, 0x69, 0x74, 0x79, 0x18, 0x12, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x61, + 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x45, 0x71, 0x75, 0x69, 0x74, 0x79, 0x12, 0x21, + 0x0a, 0x0c, 0x63, 0x61, 0x73, 0x68, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x13, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x61, 0x73, 0x68, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, + 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x65, 0x71, + 0x75, 0x69, 0x74, 0x79, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x64, 0x69, 0x73, 0x63, + 0x6f, 0x75, 0x6e, 0x74, 0x45, 0x71, 0x75, 0x69, 0x74, 0x79, 0x12, 0x1d, 0x0a, 0x0a, 0x65, 0x71, + 0x75, 0x69, 0x74, 0x79, 0x5f, 0x75, 0x73, 0x64, 0x18, 0x15, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, + 0x65, 0x71, 0x75, 0x69, 0x74, 0x79, 0x55, 0x73, 0x64, 0x12, 0x27, 0x0a, 0x0f, 0x69, 0x73, 0x6f, + 0x6c, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x65, 0x71, 0x75, 0x69, 0x74, 0x79, 0x18, 0x16, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0e, 0x69, 0x73, 0x6f, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x45, 0x71, 0x75, 0x69, + 0x74, 0x79, 0x12, 0x31, 0x0a, 0x14, 0x69, 0x73, 0x6f, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x6c, + 0x69, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x18, 0x17, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x13, 0x69, 0x73, 0x6f, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x4c, 0x69, 0x61, 0x62, 0x69, 0x6c, + 0x69, 0x74, 0x69, 0x65, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x69, 0x73, 0x6f, 0x6c, 0x61, 0x74, 0x65, + 0x64, 0x5f, 0x75, 0x70, 0x6c, 0x18, 0x18, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x69, 0x73, 0x6f, + 0x6c, 0x61, 0x74, 0x65, 0x64, 0x55, 0x70, 0x6c, 0x12, 0x2b, 0x0a, 0x11, 0x6e, 0x6f, 0x74, 0x69, + 0x6f, 0x6e, 0x61, 0x6c, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x72, 0x61, 0x67, 0x65, 0x18, 0x19, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x10, 0x6e, 0x6f, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x4c, 0x65, 0x76, + 0x65, 0x72, 0x61, 0x67, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x65, + 0x71, 0x75, 0x69, 0x74, 0x79, 0x18, 0x1a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x74, 0x6f, 0x74, + 0x61, 0x6c, 0x45, 0x71, 0x75, 0x69, 0x74, 0x79, 0x12, 0x27, 0x0a, 0x0f, 0x73, 0x74, 0x72, 0x61, + 0x74, 0x65, 0x67, 0x79, 0x5f, 0x65, 0x71, 0x75, 0x69, 0x74, 0x79, 0x18, 0x1b, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0e, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x45, 0x71, 0x75, 0x69, 0x74, + 0x79, 0x22, 0x84, 0x06, 0x0a, 0x0e, 0x46, 0x75, 0x74, 0x75, 0x72, 0x65, 0x50, 0x6f, 0x73, 0x69, + 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x12, 0x28, 0x0a, 0x04, 0x70, 0x61, 0x69, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x50, 0x61, 0x69, 0x72, 0x52, 0x04, 0x70, 0x61, 0x69, 0x72, + 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x6f, 0x70, 0x65, 0x6e, + 0x69, 0x6e, 0x67, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, + 0x6f, 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x44, 0x61, 0x74, 0x65, 0x12, 0x2b, 0x0a, 0x11, 0x6f, + 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x6f, 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x44, + 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x6f, 0x70, 0x65, 0x6e, + 0x69, 0x6e, 0x67, 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0c, 0x6f, 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, 0x21, 0x0a, + 0x0c, 0x6f, 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x08, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6f, 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x53, 0x69, 0x7a, 0x65, + 0x12, 0x2b, 0x0a, 0x11, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x64, 0x69, 0x72, 0x65, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x63, 0x75, 0x72, + 0x72, 0x65, 0x6e, 0x74, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x23, 0x0a, + 0x0d, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x0a, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x50, 0x72, 0x69, + 0x63, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x69, + 0x7a, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, + 0x74, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x75, 0x6e, 0x72, 0x65, 0x61, 0x6c, 0x69, + 0x73, 0x65, 0x64, 0x5f, 0x70, 0x6e, 0x6c, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x75, + 0x6e, 0x72, 0x65, 0x61, 0x6c, 0x69, 0x73, 0x65, 0x64, 0x50, 0x6e, 0x6c, 0x12, 0x21, 0x0a, 0x0c, + 0x72, 0x65, 0x61, 0x6c, 0x69, 0x73, 0x65, 0x64, 0x5f, 0x70, 0x6e, 0x6c, 0x18, 0x0d, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0b, 0x72, 0x65, 0x61, 0x6c, 0x69, 0x73, 0x65, 0x64, 0x50, 0x6e, 0x6c, 0x12, + 0x21, 0x0a, 0x0c, 0x63, 0x6c, 0x6f, 0x73, 0x69, 0x6e, 0x67, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, + 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6c, 0x6f, 0x73, 0x69, 0x6e, 0x67, 0x44, 0x61, + 0x74, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x43, 0x6f, + 0x75, 0x6e, 0x74, 0x12, 0x38, 0x0a, 0x18, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x5f, + 0x73, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, + 0x10, 0x20, 0x01, 0x28, 0x09, 0x52, 0x16, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x53, + 0x65, 0x74, 0x74, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x2c, 0x0a, + 0x06, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x18, 0x11, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, + 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x44, 0x65, 0x74, 0x61, + 0x69, 0x6c, 0x73, 0x52, 0x06, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x12, 0x43, 0x0a, 0x0e, 0x70, + 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x73, 0x18, 0x12, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, 0x74, + 0x75, 0x72, 0x65, 0x73, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, + 0x73, 0x52, 0x0d, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x73, + 0x12, 0x36, 0x0a, 0x0c, 0x66, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x64, 0x61, 0x74, 0x61, + 0x18, 0x13, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, + 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x44, 0x61, 0x74, 0x61, 0x52, 0x0b, 0x66, 0x75, 0x6e, + 0x64, 0x69, 0x6e, 0x67, 0x44, 0x61, 0x74, 0x61, 0x22, 0xd3, 0x02, 0x0a, 0x19, 0x47, 0x65, 0x74, + 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, + 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, + 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x12, 0x28, 0x0a, 0x04, 0x70, 0x61, 0x69, 0x72, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, + 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x50, 0x61, 0x69, 0x72, 0x52, 0x04, 0x70, 0x61, + 0x69, 0x72, 0x12, 0x35, 0x0a, 0x17, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x66, 0x75, + 0x6c, 0x6c, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x14, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x46, 0x75, 0x6c, 0x6c, + 0x4f, 0x72, 0x64, 0x65, 0x72, 0x44, 0x61, 0x74, 0x61, 0x12, 0x30, 0x0a, 0x14, 0x67, 0x65, 0x74, + 0x5f, 0x66, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, + 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x67, 0x65, 0x74, 0x46, 0x75, 0x6e, 0x64, + 0x69, 0x6e, 0x67, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x3b, 0x0a, 0x1a, 0x69, + 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x66, 0x75, 0x6c, 0x6c, 0x5f, 0x66, 0x75, 0x6e, 0x64, + 0x69, 0x6e, 0x67, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x17, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x46, 0x75, 0x6c, 0x6c, 0x46, 0x75, 0x6e, 0x64, + 0x69, 0x6e, 0x67, 0x52, 0x61, 0x74, 0x65, 0x73, 0x12, 0x34, 0x0a, 0x16, 0x69, 0x6e, 0x63, 0x6c, + 0x75, 0x64, 0x65, 0x5f, 0x70, 0x72, 0x65, 0x64, 0x69, 0x63, 0x74, 0x65, 0x64, 0x5f, 0x72, 0x61, + 0x74, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x14, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, + 0x65, 0x50, 0x72, 0x65, 0x64, 0x69, 0x63, 0x74, 0x65, 0x64, 0x52, 0x61, 0x74, 0x65, 0x22, 0xfb, + 0x01, 0x0a, 0x1d, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, + 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x35, 0x0a, 0x17, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x66, 0x75, 0x6c, 0x6c, - 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x14, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x46, 0x75, 0x6c, 0x6c, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x44, 0x61, 0x74, 0x61, 0x12, 0x30, 0x0a, 0x14, 0x67, 0x65, 0x74, 0x5f, 0x66, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x67, 0x65, 0x74, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, + 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x67, 0x65, 0x74, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x3b, 0x0a, 0x1a, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x66, 0x75, 0x6c, 0x6c, 0x5f, 0x66, 0x75, 0x6e, 0x64, 0x69, 0x6e, - 0x67, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x17, 0x69, + 0x67, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x17, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x46, 0x75, 0x6c, 0x6c, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x61, 0x74, 0x65, 0x73, 0x12, 0x34, 0x0a, 0x16, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x70, 0x72, 0x65, 0x64, 0x69, 0x63, 0x74, 0x65, 0x64, 0x5f, 0x72, 0x61, 0x74, 0x65, - 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x14, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x50, - 0x72, 0x65, 0x64, 0x69, 0x63, 0x74, 0x65, 0x64, 0x52, 0x61, 0x74, 0x65, 0x22, 0xfb, 0x01, 0x0a, - 0x1d, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x50, 0x6f, - 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x35, - 0x0a, 0x17, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x66, 0x75, 0x6c, 0x6c, 0x5f, 0x6f, - 0x72, 0x64, 0x65, 0x72, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x14, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x46, 0x75, 0x6c, 0x6c, 0x4f, 0x72, 0x64, 0x65, - 0x72, 0x44, 0x61, 0x74, 0x61, 0x12, 0x30, 0x0a, 0x14, 0x67, 0x65, 0x74, 0x5f, 0x66, 0x75, 0x6e, - 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x12, 0x67, 0x65, 0x74, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x50, - 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x3b, 0x0a, 0x1a, 0x69, 0x6e, 0x63, 0x6c, 0x75, - 0x64, 0x65, 0x5f, 0x66, 0x75, 0x6c, 0x6c, 0x5f, 0x66, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, - 0x72, 0x61, 0x74, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x17, 0x69, 0x6e, 0x63, - 0x6c, 0x75, 0x64, 0x65, 0x46, 0x75, 0x6c, 0x6c, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x52, - 0x61, 0x74, 0x65, 0x73, 0x12, 0x34, 0x0a, 0x16, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, - 0x70, 0x72, 0x65, 0x64, 0x69, 0x63, 0x74, 0x65, 0x64, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x14, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x50, 0x72, 0x65, - 0x64, 0x69, 0x63, 0x74, 0x65, 0x64, 0x52, 0x61, 0x74, 0x65, 0x22, 0x53, 0x0a, 0x1b, 0x47, 0x65, - 0x74, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x09, 0x70, 0x6f, 0x73, - 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x67, - 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, 0x74, 0x75, 0x72, 0x65, 0x50, 0x6f, 0x73, 0x69, - 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x09, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, - 0xbe, 0x01, 0x0a, 0x21, 0x47, 0x65, 0x74, 0x46, 0x75, 0x74, 0x75, 0x72, 0x65, 0x73, 0x50, 0x6f, - 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, - 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, - 0x65, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x12, 0x28, 0x0a, 0x04, 0x70, 0x61, 0x69, 0x72, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, - 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x50, 0x61, 0x69, 0x72, 0x52, 0x04, 0x70, 0x61, 0x69, - 0x72, 0x12, 0x3d, 0x0a, 0x0f, 0x75, 0x6e, 0x64, 0x65, 0x72, 0x6c, 0x79, 0x69, 0x6e, 0x67, 0x5f, - 0x70, 0x61, 0x69, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x63, 0x74, - 0x72, 0x70, 0x63, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x50, 0x61, 0x69, 0x72, - 0x52, 0x0e, 0x75, 0x6e, 0x64, 0x65, 0x72, 0x6c, 0x79, 0x69, 0x6e, 0x67, 0x50, 0x61, 0x69, 0x72, - 0x22, 0xc5, 0x01, 0x0a, 0x22, 0x47, 0x65, 0x74, 0x46, 0x75, 0x74, 0x75, 0x72, 0x65, 0x73, 0x50, - 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x14, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x50, + 0x72, 0x65, 0x64, 0x69, 0x63, 0x74, 0x65, 0x64, 0x52, 0x61, 0x74, 0x65, 0x22, 0x53, 0x0a, 0x1b, + 0x47, 0x65, 0x74, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x09, 0x70, + 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, + 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, 0x74, 0x75, 0x72, 0x65, 0x50, 0x6f, + 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x09, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x22, 0xbe, 0x01, 0x0a, 0x21, 0x47, 0x65, 0x74, 0x46, 0x75, 0x74, 0x75, 0x72, 0x65, 0x73, + 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x12, 0x28, 0x0a, 0x04, 0x70, 0x61, 0x69, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x50, 0x61, 0x69, 0x72, 0x52, 0x04, 0x70, - 0x61, 0x69, 0x72, 0x12, 0x43, 0x0a, 0x0e, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x5f, - 0x73, 0x74, 0x61, 0x74, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x63, - 0x74, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, 0x74, 0x75, 0x72, 0x65, 0x73, 0x50, 0x6f, 0x73, 0x69, - 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x0d, 0x70, 0x6f, 0x73, 0x69, 0x74, - 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x22, 0xef, 0x02, 0x0a, 0x20, 0x47, 0x65, 0x74, - 0x46, 0x75, 0x74, 0x75, 0x72, 0x65, 0x73, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, - 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, - 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x73, 0x73, - 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x12, - 0x28, 0x0a, 0x04, 0x70, 0x61, 0x69, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, - 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x50, - 0x61, 0x69, 0x72, 0x52, 0x04, 0x70, 0x61, 0x69, 0x72, 0x12, 0x3d, 0x0a, 0x0f, 0x75, 0x6e, 0x64, - 0x65, 0x72, 0x6c, 0x79, 0x69, 0x6e, 0x67, 0x5f, 0x70, 0x61, 0x69, 0x72, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x75, 0x72, 0x72, - 0x65, 0x6e, 0x63, 0x79, 0x50, 0x61, 0x69, 0x72, 0x52, 0x0e, 0x75, 0x6e, 0x64, 0x65, 0x72, 0x6c, - 0x79, 0x69, 0x6e, 0x67, 0x50, 0x61, 0x69, 0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, - 0x74, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x74, - 0x61, 0x72, 0x74, 0x44, 0x61, 0x74, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x5f, 0x64, - 0x61, 0x74, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x65, 0x6e, 0x64, 0x44, 0x61, - 0x74, 0x65, 0x12, 0x3f, 0x0a, 0x1c, 0x72, 0x65, 0x73, 0x70, 0x65, 0x63, 0x74, 0x5f, 0x6f, 0x72, - 0x64, 0x65, 0x72, 0x5f, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x5f, 0x6c, 0x69, 0x6d, 0x69, - 0x74, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x19, 0x72, 0x65, 0x73, 0x70, 0x65, 0x63, - 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x4c, 0x69, 0x6d, - 0x69, 0x74, 0x73, 0x12, 0x35, 0x0a, 0x17, 0x73, 0x79, 0x6e, 0x63, 0x5f, 0x77, 0x69, 0x74, 0x68, - 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x18, 0x08, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x14, 0x73, 0x79, 0x6e, 0x63, 0x57, 0x69, 0x74, 0x68, 0x4f, 0x72, - 0x64, 0x65, 0x72, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x22, 0x59, 0x0a, 0x21, 0x47, 0x65, - 0x74, 0x46, 0x75, 0x74, 0x75, 0x72, 0x65, 0x73, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, - 0x73, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x34, 0x0a, 0x09, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x06, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, 0x74, 0x75, - 0x72, 0x65, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x09, 0x70, 0x6f, 0x73, 0x69, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x4c, 0x0a, 0x18, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6c, 0x6c, - 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, 0x4d, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x14, 0x0a, - 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x73, - 0x73, 0x65, 0x74, 0x22, 0x76, 0x0a, 0x19, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6c, 0x6c, 0x61, 0x74, - 0x65, 0x72, 0x61, 0x6c, 0x4d, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, - 0x61, 0x73, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x73, 0x73, - 0x65, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x63, 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, - 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x63, 0x6f, 0x6c, - 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, 0x4d, 0x6f, 0x64, 0x65, 0x22, 0x75, 0x0a, 0x18, 0x53, - 0x65, 0x74, 0x43, 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, 0x4d, 0x6f, 0x64, 0x65, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, - 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, - 0x6e, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x63, 0x6f, 0x6c, - 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0e, 0x63, 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, 0x4d, 0x6f, - 0x64, 0x65, 0x22, 0x67, 0x0a, 0x19, 0x53, 0x65, 0x74, 0x43, 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, - 0x72, 0x61, 0x6c, 0x4d, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x61, 0x69, 0x72, 0x12, 0x3d, 0x0a, 0x0f, 0x75, 0x6e, 0x64, 0x65, 0x72, 0x6c, 0x79, 0x69, 0x6e, + 0x67, 0x5f, 0x70, 0x61, 0x69, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, + 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x50, 0x61, + 0x69, 0x72, 0x52, 0x0e, 0x75, 0x6e, 0x64, 0x65, 0x72, 0x6c, 0x79, 0x69, 0x6e, 0x67, 0x50, 0x61, + 0x69, 0x72, 0x22, 0xc5, 0x01, 0x0a, 0x22, 0x47, 0x65, 0x74, 0x46, 0x75, 0x74, 0x75, 0x72, 0x65, + 0x73, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, + 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, + 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, + 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x12, 0x28, 0x0a, 0x04, 0x70, + 0x61, 0x69, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x63, 0x74, 0x72, + 0x70, 0x63, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x50, 0x61, 0x69, 0x72, 0x52, + 0x04, 0x70, 0x61, 0x69, 0x72, 0x12, 0x43, 0x0a, 0x0e, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, + 0x6e, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, + 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, 0x74, 0x75, 0x72, 0x65, 0x73, 0x50, 0x6f, + 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x0d, 0x70, 0x6f, 0x73, + 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x22, 0xef, 0x02, 0x0a, 0x20, 0x47, + 0x65, 0x74, 0x46, 0x75, 0x74, 0x75, 0x72, 0x65, 0x73, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x73, 0x73, 0x65, - 0x74, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x22, 0x72, 0x0a, 0x14, 0x47, - 0x65, 0x74, 0x4d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x65, 0x71, 0x75, + 0x74, 0x12, 0x28, 0x0a, 0x04, 0x70, 0x61, 0x69, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x14, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, + 0x79, 0x50, 0x61, 0x69, 0x72, 0x52, 0x04, 0x70, 0x61, 0x69, 0x72, 0x12, 0x3d, 0x0a, 0x0f, 0x75, + 0x6e, 0x64, 0x65, 0x72, 0x6c, 0x79, 0x69, 0x6e, 0x67, 0x5f, 0x70, 0x61, 0x69, 0x72, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x75, + 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x50, 0x61, 0x69, 0x72, 0x52, 0x0e, 0x75, 0x6e, 0x64, 0x65, + 0x72, 0x6c, 0x79, 0x69, 0x6e, 0x67, 0x50, 0x61, 0x69, 0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x74, + 0x61, 0x72, 0x74, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, + 0x73, 0x74, 0x61, 0x72, 0x74, 0x44, 0x61, 0x74, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x65, 0x6e, 0x64, + 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x65, 0x6e, 0x64, + 0x44, 0x61, 0x74, 0x65, 0x12, 0x3f, 0x0a, 0x1c, 0x72, 0x65, 0x73, 0x70, 0x65, 0x63, 0x74, 0x5f, + 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x5f, 0x6c, 0x69, + 0x6d, 0x69, 0x74, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x19, 0x72, 0x65, 0x73, 0x70, + 0x65, 0x63, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x4c, + 0x69, 0x6d, 0x69, 0x74, 0x73, 0x12, 0x35, 0x0a, 0x17, 0x73, 0x79, 0x6e, 0x63, 0x5f, 0x77, 0x69, + 0x74, 0x68, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, + 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x14, 0x73, 0x79, 0x6e, 0x63, 0x57, 0x69, 0x74, 0x68, + 0x4f, 0x72, 0x64, 0x65, 0x72, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x22, 0x59, 0x0a, 0x21, + 0x47, 0x65, 0x74, 0x46, 0x75, 0x74, 0x75, 0x72, 0x65, 0x73, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x34, 0x0a, 0x09, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x06, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, + 0x74, 0x75, 0x72, 0x65, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x09, 0x70, 0x6f, + 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x4c, 0x0a, 0x18, 0x47, 0x65, 0x74, 0x43, 0x6f, + 0x6c, 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, 0x4d, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, - 0x61, 0x73, 0x73, 0x65, 0x74, 0x12, 0x28, 0x0a, 0x04, 0x70, 0x61, 0x69, 0x72, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x75, 0x72, - 0x72, 0x65, 0x6e, 0x63, 0x79, 0x50, 0x61, 0x69, 0x72, 0x52, 0x04, 0x70, 0x61, 0x69, 0x72, 0x22, - 0x94, 0x01, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x54, 0x79, 0x70, - 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, + 0x61, 0x73, 0x73, 0x65, 0x74, 0x22, 0x76, 0x0a, 0x19, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6c, 0x6c, + 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, 0x4d, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x14, + 0x0a, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, + 0x73, 0x73, 0x65, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x63, 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, 0x72, + 0x61, 0x6c, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x63, + 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, 0x4d, 0x6f, 0x64, 0x65, 0x22, 0x75, 0x0a, + 0x18, 0x53, 0x65, 0x74, 0x43, 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, 0x4d, 0x6f, + 0x64, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x12, 0x28, 0x0a, 0x04, 0x70, - 0x61, 0x69, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x63, 0x74, 0x72, - 0x70, 0x63, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x50, 0x61, 0x69, 0x72, 0x52, - 0x04, 0x70, 0x61, 0x69, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x5f, - 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6d, 0x61, 0x72, 0x67, - 0x69, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x22, 0xa9, 0x02, 0x0a, 0x1b, 0x43, 0x68, 0x61, 0x6e, 0x67, - 0x65, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, - 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, - 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x12, 0x28, 0x0a, 0x04, 0x70, 0x61, 0x69, 0x72, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, - 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x50, 0x61, 0x69, 0x72, 0x52, 0x04, 0x70, 0x61, - 0x69, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x5f, 0x74, 0x79, 0x70, - 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x54, - 0x79, 0x70, 0x65, 0x12, 0x3a, 0x0a, 0x19, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x5f, - 0x61, 0x6c, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, - 0x18, 0x05, 0x20, 0x01, 0x28, 0x01, 0x52, 0x17, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, - 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x65, 0x64, 0x4d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x12, - 0x30, 0x0a, 0x14, 0x6e, 0x65, 0x77, 0x5f, 0x61, 0x6c, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x65, 0x64, - 0x5f, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x01, 0x52, 0x12, 0x6e, - 0x65, 0x77, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x65, 0x64, 0x4d, 0x61, 0x72, 0x67, 0x69, - 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x5f, 0x73, 0x69, 0x64, 0x65, - 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x53, 0x69, - 0x64, 0x65, 0x22, 0xee, 0x01, 0x0a, 0x1c, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x50, 0x6f, 0x73, - 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, - 0x14, 0x0a, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, - 0x61, 0x73, 0x73, 0x65, 0x74, 0x12, 0x28, 0x0a, 0x04, 0x70, 0x61, 0x69, 0x72, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x75, 0x72, - 0x72, 0x65, 0x6e, 0x63, 0x79, 0x50, 0x61, 0x69, 0x72, 0x52, 0x04, 0x70, 0x61, 0x69, 0x72, 0x12, - 0x1f, 0x0a, 0x0b, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x54, 0x79, 0x70, 0x65, - 0x12, 0x30, 0x0a, 0x14, 0x6e, 0x65, 0x77, 0x5f, 0x61, 0x6c, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x65, - 0x64, 0x5f, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x01, 0x52, 0x12, - 0x6e, 0x65, 0x77, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x65, 0x64, 0x4d, 0x61, 0x72, 0x67, - 0x69, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x5f, 0x73, 0x69, 0x64, - 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x53, - 0x69, 0x64, 0x65, 0x22, 0x93, 0x01, 0x0a, 0x14, 0x53, 0x65, 0x74, 0x4d, 0x61, 0x72, 0x67, 0x69, - 0x6e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, - 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, - 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x73, 0x73, 0x65, - 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x12, 0x28, - 0x0a, 0x04, 0x70, 0x61, 0x69, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, - 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x50, 0x61, - 0x69, 0x72, 0x52, 0x04, 0x70, 0x61, 0x69, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x6d, 0x61, 0x72, 0x67, - 0x69, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6d, - 0x61, 0x72, 0x67, 0x69, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x22, 0x8d, 0x01, 0x0a, 0x15, 0x53, 0x65, - 0x74, 0x4d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, - 0x14, 0x0a, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, - 0x61, 0x73, 0x73, 0x65, 0x74, 0x12, 0x28, 0x0a, 0x04, 0x70, 0x61, 0x69, 0x72, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x75, 0x72, - 0x72, 0x65, 0x6e, 0x63, 0x79, 0x50, 0x61, 0x69, 0x72, 0x52, 0x04, 0x70, 0x61, 0x69, 0x72, 0x12, - 0x18, 0x0a, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x22, 0xef, 0x01, 0x0a, 0x12, 0x47, 0x65, - 0x74, 0x4c, 0x65, 0x76, 0x65, 0x72, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, - 0x61, 0x73, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x73, 0x73, - 0x65, 0x74, 0x12, 0x28, 0x0a, 0x04, 0x70, 0x61, 0x69, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x14, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, - 0x63, 0x79, 0x50, 0x61, 0x69, 0x72, 0x52, 0x04, 0x70, 0x61, 0x69, 0x72, 0x12, 0x3d, 0x0a, 0x0f, - 0x75, 0x6e, 0x64, 0x65, 0x72, 0x6c, 0x79, 0x69, 0x6e, 0x67, 0x5f, 0x70, 0x61, 0x69, 0x72, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, - 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x50, 0x61, 0x69, 0x72, 0x52, 0x0e, 0x75, 0x6e, 0x64, - 0x65, 0x72, 0x6c, 0x79, 0x69, 0x6e, 0x67, 0x50, 0x61, 0x69, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x6d, - 0x61, 0x72, 0x67, 0x69, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0a, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x0a, - 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x73, 0x69, 0x64, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x09, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x53, 0x69, 0x64, 0x65, 0x22, 0x8c, 0x02, 0x0a, 0x13, - 0x47, 0x65, 0x74, 0x4c, 0x65, 0x76, 0x65, 0x72, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x63, + 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x63, 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, + 0x4d, 0x6f, 0x64, 0x65, 0x22, 0x67, 0x0a, 0x19, 0x53, 0x65, 0x74, 0x43, 0x6f, 0x6c, 0x6c, 0x61, + 0x74, 0x65, 0x72, 0x61, 0x6c, 0x4d, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x14, 0x0a, + 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x73, + 0x73, 0x65, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x22, 0x72, 0x0a, + 0x14, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, + 0x65, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x12, 0x28, 0x0a, 0x04, 0x70, 0x61, 0x69, 0x72, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, + 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x50, 0x61, 0x69, 0x72, 0x52, 0x04, 0x70, 0x61, 0x69, + 0x72, 0x22, 0x94, 0x01, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x54, + 0x79, 0x70, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x65, + 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, + 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x12, 0x28, 0x0a, + 0x04, 0x70, 0x61, 0x69, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x63, + 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x50, 0x61, 0x69, + 0x72, 0x52, 0x04, 0x70, 0x61, 0x69, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x6d, 0x61, 0x72, 0x67, 0x69, + 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6d, 0x61, + 0x72, 0x67, 0x69, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x22, 0xa9, 0x02, 0x0a, 0x1b, 0x43, 0x68, 0x61, + 0x6e, 0x67, 0x65, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x61, 0x72, 0x67, 0x69, + 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, + 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, + 0x61, 0x6e, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x12, 0x28, 0x0a, 0x04, 0x70, 0x61, + 0x69, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, + 0x63, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x50, 0x61, 0x69, 0x72, 0x52, 0x04, + 0x70, 0x61, 0x69, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x5f, 0x74, + 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6d, 0x61, 0x72, 0x67, 0x69, + 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x3a, 0x0a, 0x19, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, + 0x6c, 0x5f, 0x61, 0x6c, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x6d, 0x61, 0x72, 0x67, + 0x69, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x01, 0x52, 0x17, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, + 0x61, 0x6c, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x65, 0x64, 0x4d, 0x61, 0x72, 0x67, 0x69, + 0x6e, 0x12, 0x30, 0x0a, 0x14, 0x6e, 0x65, 0x77, 0x5f, 0x61, 0x6c, 0x6c, 0x6f, 0x63, 0x61, 0x74, + 0x65, 0x64, 0x5f, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x01, 0x52, + 0x12, 0x6e, 0x65, 0x77, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x65, 0x64, 0x4d, 0x61, 0x72, + 0x67, 0x69, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x5f, 0x73, 0x69, + 0x64, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, + 0x53, 0x69, 0x64, 0x65, 0x22, 0xee, 0x01, 0x0a, 0x1c, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x50, + 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, + 0x65, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x12, 0x28, 0x0a, 0x04, 0x70, 0x61, 0x69, 0x72, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, + 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x50, 0x61, 0x69, 0x72, 0x52, 0x04, 0x70, 0x61, 0x69, + 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x54, 0x79, + 0x70, 0x65, 0x12, 0x30, 0x0a, 0x14, 0x6e, 0x65, 0x77, 0x5f, 0x61, 0x6c, 0x6c, 0x6f, 0x63, 0x61, + 0x74, 0x65, 0x64, 0x5f, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x01, + 0x52, 0x12, 0x6e, 0x65, 0x77, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x65, 0x64, 0x4d, 0x61, + 0x72, 0x67, 0x69, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x5f, 0x73, + 0x69, 0x64, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6d, 0x61, 0x72, 0x67, 0x69, + 0x6e, 0x53, 0x69, 0x64, 0x65, 0x22, 0x93, 0x01, 0x0a, 0x14, 0x53, 0x65, 0x74, 0x4d, 0x61, 0x72, + 0x67, 0x69, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, + 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x73, + 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, + 0x12, 0x28, 0x0a, 0x04, 0x70, 0x61, 0x69, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, + 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, + 0x50, 0x61, 0x69, 0x72, 0x52, 0x04, 0x70, 0x61, 0x69, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x6d, 0x61, + 0x72, 0x67, 0x69, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0a, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x22, 0x8d, 0x01, 0x0a, 0x15, + 0x53, 0x65, 0x74, 0x4d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, + 0x65, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x12, 0x28, 0x0a, 0x04, 0x70, 0x61, 0x69, 0x72, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, + 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x50, 0x61, 0x69, 0x72, 0x52, 0x04, 0x70, 0x61, 0x69, + 0x72, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x22, 0xef, 0x01, 0x0a, 0x12, + 0x47, 0x65, 0x74, 0x4c, 0x65, 0x76, 0x65, 0x72, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x14, + 0x0a, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, + 0x73, 0x73, 0x65, 0x74, 0x12, 0x28, 0x0a, 0x04, 0x70, 0x61, 0x69, 0x72, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x75, 0x72, 0x72, + 0x65, 0x6e, 0x63, 0x79, 0x50, 0x61, 0x69, 0x72, 0x52, 0x04, 0x70, 0x61, 0x69, 0x72, 0x12, 0x3d, + 0x0a, 0x0f, 0x75, 0x6e, 0x64, 0x65, 0x72, 0x6c, 0x79, 0x69, 0x6e, 0x67, 0x5f, 0x70, 0x61, 0x69, + 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, + 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x50, 0x61, 0x69, 0x72, 0x52, 0x0e, 0x75, + 0x6e, 0x64, 0x65, 0x72, 0x6c, 0x79, 0x69, 0x6e, 0x67, 0x50, 0x61, 0x69, 0x72, 0x12, 0x1f, 0x0a, + 0x0b, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0a, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, + 0x0a, 0x0a, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x73, 0x69, 0x64, 0x65, 0x18, 0x06, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x09, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x53, 0x69, 0x64, 0x65, 0x22, 0x8c, 0x02, + 0x0a, 0x13, 0x47, 0x65, 0x74, 0x4c, 0x65, 0x76, 0x65, 0x72, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, + 0x65, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x12, 0x28, 0x0a, 0x04, 0x70, 0x61, 0x69, 0x72, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, + 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x50, 0x61, 0x69, 0x72, 0x52, 0x04, 0x70, 0x61, 0x69, + 0x72, 0x12, 0x3d, 0x0a, 0x0f, 0x75, 0x6e, 0x64, 0x65, 0x72, 0x6c, 0x79, 0x69, 0x6e, 0x67, 0x5f, + 0x70, 0x61, 0x69, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x63, 0x74, + 0x72, 0x70, 0x63, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x50, 0x61, 0x69, 0x72, + 0x52, 0x0e, 0x75, 0x6e, 0x64, 0x65, 0x72, 0x6c, 0x79, 0x69, 0x6e, 0x67, 0x50, 0x61, 0x69, 0x72, + 0x12, 0x1f, 0x0a, 0x0b, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x54, 0x79, 0x70, + 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x6c, 0x65, 0x76, 0x65, 0x72, 0x61, 0x67, 0x65, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x01, 0x52, 0x08, 0x6c, 0x65, 0x76, 0x65, 0x72, 0x61, 0x67, 0x65, 0x12, 0x1d, 0x0a, + 0x0a, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x73, 0x69, 0x64, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x09, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x53, 0x69, 0x64, 0x65, 0x22, 0x8b, 0x02, 0x0a, + 0x12, 0x53, 0x65, 0x74, 0x4c, 0x65, 0x76, 0x65, 0x72, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x12, 0x28, 0x0a, 0x04, 0x70, 0x61, 0x69, 0x72, 0x18, 0x03, 0x20, @@ -17586,1432 +17603,1415 @@ var file_rpc_proto_rawDesc = []byte{ 0x1a, 0x0a, 0x08, 0x6c, 0x65, 0x76, 0x65, 0x72, 0x61, 0x67, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x01, 0x52, 0x08, 0x6c, 0x65, 0x76, 0x65, 0x72, 0x61, 0x67, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x73, 0x69, 0x64, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x09, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x53, 0x69, 0x64, 0x65, 0x22, 0x8b, 0x02, 0x0a, 0x12, 0x53, - 0x65, 0x74, 0x4c, 0x65, 0x76, 0x65, 0x72, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x14, 0x0a, - 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x73, - 0x73, 0x65, 0x74, 0x12, 0x28, 0x0a, 0x04, 0x70, 0x61, 0x69, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, - 0x6e, 0x63, 0x79, 0x50, 0x61, 0x69, 0x72, 0x52, 0x04, 0x70, 0x61, 0x69, 0x72, 0x12, 0x3d, 0x0a, - 0x0f, 0x75, 0x6e, 0x64, 0x65, 0x72, 0x6c, 0x79, 0x69, 0x6e, 0x67, 0x5f, 0x70, 0x61, 0x69, 0x72, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, - 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x50, 0x61, 0x69, 0x72, 0x52, 0x0e, 0x75, 0x6e, - 0x64, 0x65, 0x72, 0x6c, 0x79, 0x69, 0x6e, 0x67, 0x50, 0x61, 0x69, 0x72, 0x12, 0x1f, 0x0a, 0x0b, - 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0a, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1a, 0x0a, - 0x08, 0x6c, 0x65, 0x76, 0x65, 0x72, 0x61, 0x67, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x01, 0x52, - 0x08, 0x6c, 0x65, 0x76, 0x65, 0x72, 0x61, 0x67, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x6f, 0x72, 0x64, - 0x65, 0x72, 0x5f, 0x73, 0x69, 0x64, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6f, - 0x72, 0x64, 0x65, 0x72, 0x53, 0x69, 0x64, 0x65, 0x22, 0x8a, 0x02, 0x0a, 0x13, 0x53, 0x65, 0x74, - 0x4c, 0x65, 0x76, 0x65, 0x72, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x09, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x53, 0x69, 0x64, 0x65, 0x22, 0x8a, 0x02, 0x0a, 0x13, 0x53, + 0x65, 0x74, 0x4c, 0x65, 0x76, 0x65, 0x72, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x14, + 0x0a, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, + 0x73, 0x73, 0x65, 0x74, 0x12, 0x28, 0x0a, 0x04, 0x70, 0x61, 0x69, 0x72, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x75, 0x72, 0x72, + 0x65, 0x6e, 0x63, 0x79, 0x50, 0x61, 0x69, 0x72, 0x52, 0x04, 0x70, 0x61, 0x69, 0x72, 0x12, 0x3d, + 0x0a, 0x0f, 0x75, 0x6e, 0x64, 0x65, 0x72, 0x6c, 0x79, 0x69, 0x6e, 0x67, 0x5f, 0x70, 0x61, 0x69, + 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, + 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x50, 0x61, 0x69, 0x72, 0x52, 0x0e, 0x75, + 0x6e, 0x64, 0x65, 0x72, 0x6c, 0x79, 0x69, 0x6e, 0x67, 0x50, 0x61, 0x69, 0x72, 0x12, 0x1f, 0x0a, + 0x0b, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0a, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, + 0x0a, 0x0a, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x73, 0x69, 0x64, 0x65, 0x18, 0x06, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x09, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x53, 0x69, 0x64, 0x65, 0x12, 0x18, 0x0a, + 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, + 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x22, 0xd2, 0x01, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x43, + 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x73, 0x73, - 0x65, 0x74, 0x12, 0x28, 0x0a, 0x04, 0x70, 0x61, 0x69, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x14, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, - 0x63, 0x79, 0x50, 0x61, 0x69, 0x72, 0x52, 0x04, 0x70, 0x61, 0x69, 0x72, 0x12, 0x3d, 0x0a, 0x0f, - 0x75, 0x6e, 0x64, 0x65, 0x72, 0x6c, 0x79, 0x69, 0x6e, 0x67, 0x5f, 0x70, 0x61, 0x69, 0x72, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, - 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x50, 0x61, 0x69, 0x72, 0x52, 0x0e, 0x75, 0x6e, 0x64, - 0x65, 0x72, 0x6c, 0x79, 0x69, 0x6e, 0x67, 0x50, 0x61, 0x69, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x6d, - 0x61, 0x72, 0x67, 0x69, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0a, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x0a, - 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x73, 0x69, 0x64, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x09, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x53, 0x69, 0x64, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, - 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x75, - 0x63, 0x63, 0x65, 0x73, 0x73, 0x22, 0xd2, 0x01, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6c, - 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, - 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x73, - 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, - 0x12, 0x2b, 0x0a, 0x11, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x62, 0x72, 0x65, 0x61, - 0x6b, 0x64, 0x6f, 0x77, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x69, 0x6e, 0x63, - 0x6c, 0x75, 0x64, 0x65, 0x42, 0x72, 0x65, 0x61, 0x6b, 0x64, 0x6f, 0x77, 0x6e, 0x12, 0x2b, 0x0a, - 0x11, 0x63, 0x61, 0x6c, 0x63, 0x75, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x6f, 0x66, 0x66, 0x6c, 0x69, - 0x6e, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x63, 0x61, 0x6c, 0x63, 0x75, 0x6c, - 0x61, 0x74, 0x65, 0x4f, 0x66, 0x66, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x2e, 0x0a, 0x13, 0x69, 0x6e, - 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x7a, 0x65, 0x72, 0x6f, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, - 0x5a, 0x65, 0x72, 0x6f, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x22, 0xbe, 0x05, 0x0a, 0x15, 0x47, - 0x65, 0x74, 0x43, 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x75, 0x62, 0x5f, 0x61, 0x63, 0x63, 0x6f, - 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x75, 0x62, 0x41, 0x63, - 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2f, 0x0a, 0x13, 0x63, 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, - 0x72, 0x61, 0x6c, 0x5f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x12, 0x63, 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, 0x43, 0x75, - 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x4f, 0x0a, 0x25, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, - 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x6f, 0x66, 0x5f, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x76, - 0x65, 0x5f, 0x73, 0x70, 0x6f, 0x74, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x20, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x56, 0x61, 0x6c, 0x75, - 0x65, 0x4f, 0x66, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x53, 0x70, 0x6f, 0x74, 0x42, - 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x65, 0x0a, 0x30, 0x63, 0x6f, 0x6c, 0x6c, 0x61, - 0x74, 0x65, 0x72, 0x61, 0x6c, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, - 0x64, 0x5f, 0x62, 0x79, 0x5f, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x73, 0x70, - 0x6f, 0x74, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x2b, 0x63, 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, 0x43, 0x6f, 0x6e, - 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x64, 0x42, 0x79, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, - 0x76, 0x65, 0x53, 0x70, 0x6f, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x27, - 0x0a, 0x0f, 0x75, 0x73, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, - 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x75, 0x73, 0x65, 0x64, 0x43, 0x6f, 0x6c, - 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, 0x12, 0x46, 0x0a, 0x0e, 0x75, 0x73, 0x65, 0x64, 0x5f, - 0x62, 0x72, 0x65, 0x61, 0x6b, 0x64, 0x6f, 0x77, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1f, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, - 0x72, 0x61, 0x6c, 0x55, 0x73, 0x65, 0x64, 0x42, 0x72, 0x65, 0x61, 0x6b, 0x64, 0x6f, 0x77, 0x6e, - 0x52, 0x0d, 0x75, 0x73, 0x65, 0x64, 0x42, 0x72, 0x65, 0x61, 0x6b, 0x64, 0x6f, 0x77, 0x6e, 0x12, - 0x31, 0x0a, 0x14, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x63, 0x6f, 0x6c, - 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x61, - 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, 0x72, - 0x61, 0x6c, 0x12, 0x35, 0x0a, 0x16, 0x6d, 0x61, 0x69, 0x6e, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x63, - 0x65, 0x5f, 0x63, 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, 0x18, 0x08, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x15, 0x6d, 0x61, 0x69, 0x6e, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x63, 0x65, 0x43, - 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, 0x12, 0x25, 0x0a, 0x0e, 0x75, 0x6e, 0x72, - 0x65, 0x61, 0x6c, 0x69, 0x73, 0x65, 0x64, 0x5f, 0x70, 0x6e, 0x6c, 0x18, 0x09, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0d, 0x75, 0x6e, 0x72, 0x65, 0x61, 0x6c, 0x69, 0x73, 0x65, 0x64, 0x50, 0x6e, 0x6c, - 0x12, 0x4c, 0x0a, 0x12, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x62, 0x72, 0x65, - 0x61, 0x6b, 0x64, 0x6f, 0x77, 0x6e, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x67, - 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, - 0x46, 0x6f, 0x72, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x11, 0x63, 0x75, 0x72, - 0x72, 0x65, 0x6e, 0x63, 0x79, 0x42, 0x72, 0x65, 0x61, 0x6b, 0x64, 0x6f, 0x77, 0x6e, 0x12, 0x4b, - 0x0a, 0x12, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x62, 0x72, 0x65, 0x61, 0x6b, - 0x64, 0x6f, 0x77, 0x6e, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x63, 0x74, - 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, 0x42, 0x79, - 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x11, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, - 0x6f, 0x6e, 0x42, 0x72, 0x65, 0x61, 0x6b, 0x64, 0x6f, 0x77, 0x6e, 0x22, 0xf7, 0x04, 0x0a, 0x15, - 0x43, 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, 0x46, 0x6f, 0x72, 0x43, 0x75, 0x72, - 0x72, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, - 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, - 0x79, 0x12, 0x38, 0x0a, 0x18, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64, 0x5f, 0x66, 0x72, - 0x6f, 0x6d, 0x5f, 0x63, 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x16, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64, 0x46, 0x72, 0x6f, - 0x6d, 0x43, 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, 0x74, - 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x66, 0x75, 0x6e, 0x64, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x46, 0x75, 0x6e, 0x64, 0x73, 0x12, 0x44, 0x0a, 0x1f, - 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x66, 0x6f, 0x72, 0x5f, 0x75, 0x73, - 0x65, 0x5f, 0x61, 0x73, 0x5f, 0x63, 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x1b, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, - 0x46, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x41, 0x73, 0x43, 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, 0x72, - 0x61, 0x6c, 0x12, 0x37, 0x0a, 0x18, 0x61, 0x70, 0x70, 0x72, 0x6f, 0x78, 0x5f, 0x66, 0x61, 0x69, - 0x72, 0x5f, 0x6d, 0x61, 0x72, 0x6b, 0x65, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x15, 0x61, 0x70, 0x70, 0x72, 0x6f, 0x78, 0x46, 0x61, 0x69, 0x72, - 0x4d, 0x61, 0x72, 0x6b, 0x65, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x77, - 0x65, 0x69, 0x67, 0x68, 0x74, 0x69, 0x6e, 0x67, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, - 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x69, 0x6e, 0x67, 0x12, 0x37, 0x0a, 0x17, 0x63, 0x6f, 0x6c, + 0x65, 0x74, 0x12, 0x2b, 0x0a, 0x11, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x62, 0x72, + 0x65, 0x61, 0x6b, 0x64, 0x6f, 0x77, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x69, + 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x42, 0x72, 0x65, 0x61, 0x6b, 0x64, 0x6f, 0x77, 0x6e, 0x12, + 0x2b, 0x0a, 0x11, 0x63, 0x61, 0x6c, 0x63, 0x75, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x6f, 0x66, 0x66, + 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x63, 0x61, 0x6c, 0x63, + 0x75, 0x6c, 0x61, 0x74, 0x65, 0x4f, 0x66, 0x66, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x2e, 0x0a, 0x13, + 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x7a, 0x65, 0x72, 0x6f, 0x5f, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x69, 0x6e, 0x63, 0x6c, 0x75, + 0x64, 0x65, 0x5a, 0x65, 0x72, 0x6f, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x22, 0xbe, 0x05, 0x0a, + 0x15, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x75, 0x62, 0x5f, 0x61, 0x63, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x75, 0x62, + 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2f, 0x0a, 0x13, 0x63, 0x6f, 0x6c, 0x6c, 0x61, + 0x74, 0x65, 0x72, 0x61, 0x6c, 0x5f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x63, 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, + 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x4f, 0x0a, 0x25, 0x74, 0x6f, 0x74, 0x61, + 0x6c, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x6f, 0x66, 0x5f, 0x70, 0x6f, 0x73, 0x69, 0x74, + 0x69, 0x76, 0x65, 0x5f, 0x73, 0x70, 0x6f, 0x74, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, + 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x20, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x56, 0x61, + 0x6c, 0x75, 0x65, 0x4f, 0x66, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x53, 0x70, 0x6f, + 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x65, 0x0a, 0x30, 0x63, 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x69, 0x62, 0x75, - 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x16, 0x63, 0x6f, 0x6c, 0x6c, - 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, - 0x6f, 0x6e, 0x12, 0x2c, 0x0a, 0x12, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x64, 0x5f, 0x74, 0x6f, 0x5f, - 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, - 0x73, 0x63, 0x61, 0x6c, 0x65, 0x64, 0x54, 0x6f, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, - 0x12, 0x25, 0x0a, 0x0e, 0x75, 0x6e, 0x72, 0x65, 0x61, 0x6c, 0x69, 0x73, 0x65, 0x64, 0x5f, 0x70, - 0x6e, 0x6c, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x75, 0x6e, 0x72, 0x65, 0x61, 0x6c, - 0x69, 0x73, 0x65, 0x64, 0x50, 0x6e, 0x6c, 0x12, 0x20, 0x0a, 0x0c, 0x66, 0x75, 0x6e, 0x64, 0x73, - 0x5f, 0x69, 0x6e, 0x5f, 0x75, 0x73, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x66, - 0x75, 0x6e, 0x64, 0x73, 0x49, 0x6e, 0x55, 0x73, 0x65, 0x12, 0x3c, 0x0a, 0x1a, 0x61, 0x64, 0x64, - 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x5f, 0x63, 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, 0x72, - 0x61, 0x6c, 0x5f, 0x75, 0x73, 0x65, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x18, 0x61, - 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x43, 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, - 0x72, 0x61, 0x6c, 0x55, 0x73, 0x65, 0x64, 0x12, 0x46, 0x0a, 0x0e, 0x75, 0x73, 0x65, 0x64, 0x5f, - 0x62, 0x72, 0x65, 0x61, 0x6b, 0x64, 0x6f, 0x77, 0x6e, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1f, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, - 0x72, 0x61, 0x6c, 0x55, 0x73, 0x65, 0x64, 0x42, 0x72, 0x65, 0x61, 0x6b, 0x64, 0x6f, 0x77, 0x6e, - 0x52, 0x0d, 0x75, 0x73, 0x65, 0x64, 0x42, 0x72, 0x65, 0x61, 0x6b, 0x64, 0x6f, 0x77, 0x6e, 0x12, - 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, - 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x8f, 0x02, 0x0a, 0x14, 0x43, 0x6f, 0x6c, 0x6c, 0x61, 0x74, - 0x65, 0x72, 0x61, 0x6c, 0x42, 0x79, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1a, - 0x0a, 0x08, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x08, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, - 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x26, - 0x0a, 0x0f, 0x6f, 0x70, 0x65, 0x6e, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x73, 0x69, 0x7a, - 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6f, 0x70, 0x65, 0x6e, 0x4f, 0x72, 0x64, - 0x65, 0x72, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, - 0x6f, 0x6e, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, - 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x6d, - 0x61, 0x72, 0x6b, 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x09, 0x6d, 0x61, 0x72, 0x6b, 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x72, 0x65, - 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x5f, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x18, 0x06, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0e, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x4d, 0x61, 0x72, - 0x67, 0x69, 0x6e, 0x12, 0x32, 0x0a, 0x15, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x63, 0x6f, 0x6c, - 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, 0x5f, 0x75, 0x73, 0x65, 0x64, 0x18, 0x07, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x13, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x43, 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, - 0x72, 0x61, 0x6c, 0x55, 0x73, 0x65, 0x64, 0x22, 0xae, 0x03, 0x0a, 0x17, 0x43, 0x6f, 0x6c, 0x6c, - 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, 0x55, 0x73, 0x65, 0x64, 0x42, 0x72, 0x65, 0x61, 0x6b, 0x64, - 0x6f, 0x77, 0x6e, 0x12, 0x28, 0x0a, 0x10, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x5f, 0x69, 0x6e, - 0x5f, 0x73, 0x74, 0x61, 0x6b, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6c, - 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x49, 0x6e, 0x53, 0x74, 0x61, 0x6b, 0x65, 0x73, 0x12, 0x2b, 0x0a, - 0x12, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x5f, 0x69, 0x6e, 0x5f, 0x6e, 0x66, 0x74, 0x5f, 0x62, - 0x69, 0x64, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x6c, 0x6f, 0x63, 0x6b, 0x65, - 0x64, 0x49, 0x6e, 0x4e, 0x66, 0x74, 0x42, 0x69, 0x64, 0x73, 0x12, 0x31, 0x0a, 0x15, 0x6c, 0x6f, - 0x63, 0x6b, 0x65, 0x64, 0x5f, 0x69, 0x6e, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x76, 0x6f, 0x75, 0x63, - 0x68, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x6c, 0x6f, 0x63, 0x6b, 0x65, - 0x64, 0x49, 0x6e, 0x46, 0x65, 0x65, 0x56, 0x6f, 0x75, 0x63, 0x68, 0x65, 0x72, 0x12, 0x4d, 0x0a, - 0x24, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x5f, 0x69, 0x6e, 0x5f, 0x73, 0x70, 0x6f, 0x74, 0x5f, - 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x5f, 0x66, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x6f, - 0x66, 0x66, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x1f, 0x6c, 0x6f, 0x63, - 0x6b, 0x65, 0x64, 0x49, 0x6e, 0x53, 0x70, 0x6f, 0x74, 0x4d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x46, - 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x4f, 0x66, 0x66, 0x65, 0x72, 0x73, 0x12, 0x31, 0x0a, 0x15, - 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x5f, 0x69, 0x6e, 0x5f, 0x73, 0x70, 0x6f, 0x74, 0x5f, 0x6f, - 0x72, 0x64, 0x65, 0x72, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x6c, 0x6f, 0x63, - 0x6b, 0x65, 0x64, 0x49, 0x6e, 0x53, 0x70, 0x6f, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x12, - 0x30, 0x0a, 0x14, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x5f, 0x61, 0x73, 0x5f, 0x63, 0x6f, 0x6c, - 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x6c, - 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x41, 0x73, 0x43, 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, - 0x6c, 0x12, 0x26, 0x0a, 0x0f, 0x75, 0x73, 0x65, 0x64, 0x5f, 0x69, 0x6e, 0x5f, 0x66, 0x75, 0x74, - 0x75, 0x72, 0x65, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x75, 0x73, 0x65, 0x64, - 0x49, 0x6e, 0x46, 0x75, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x2d, 0x0a, 0x13, 0x75, 0x73, 0x65, - 0x64, 0x5f, 0x69, 0x6e, 0x5f, 0x73, 0x70, 0x6f, 0x74, 0x5f, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, - 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x75, 0x73, 0x65, 0x64, 0x49, 0x6e, 0x53, 0x70, - 0x6f, 0x74, 0x4d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x22, 0xe7, 0x02, 0x0a, 0x16, 0x47, 0x65, 0x74, - 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x61, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, - 0x14, 0x0a, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, - 0x61, 0x73, 0x73, 0x65, 0x74, 0x12, 0x28, 0x0a, 0x04, 0x70, 0x61, 0x69, 0x72, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x75, 0x72, - 0x72, 0x65, 0x6e, 0x63, 0x79, 0x50, 0x61, 0x69, 0x72, 0x52, 0x04, 0x70, 0x61, 0x69, 0x72, 0x12, - 0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x44, 0x61, 0x74, 0x65, 0x12, 0x19, - 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x07, 0x65, 0x6e, 0x64, 0x44, 0x61, 0x74, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x70, 0x61, 0x79, - 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x06, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x43, 0x75, 0x72, 0x72, - 0x65, 0x6e, 0x63, 0x79, 0x12, 0x2b, 0x0a, 0x11, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, - 0x70, 0x72, 0x65, 0x64, 0x69, 0x63, 0x74, 0x65, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x10, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x50, 0x72, 0x65, 0x64, 0x69, 0x63, 0x74, 0x65, - 0x64, 0x12, 0x29, 0x0a, 0x10, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x70, 0x61, 0x79, - 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x69, 0x6e, 0x63, - 0x6c, 0x75, 0x64, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x34, 0x0a, 0x16, - 0x72, 0x65, 0x73, 0x70, 0x65, 0x63, 0x74, 0x5f, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x5f, - 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x14, 0x72, 0x65, - 0x73, 0x70, 0x65, 0x63, 0x74, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x4c, 0x69, 0x6d, 0x69, - 0x74, 0x73, 0x22, 0x44, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, - 0x52, 0x61, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x29, 0x0a, - 0x05, 0x72, 0x61, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x67, - 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x44, 0x61, 0x74, - 0x61, 0x52, 0x05, 0x72, 0x61, 0x74, 0x65, 0x73, 0x22, 0xa6, 0x01, 0x0a, 0x1b, 0x47, 0x65, 0x74, - 0x4c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x61, 0x74, - 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, - 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, - 0x61, 0x6e, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x12, 0x28, 0x0a, 0x04, 0x70, 0x61, - 0x69, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, - 0x63, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x50, 0x61, 0x69, 0x72, 0x52, 0x04, - 0x70, 0x61, 0x69, 0x72, 0x12, 0x2b, 0x0a, 0x11, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, - 0x70, 0x72, 0x65, 0x64, 0x69, 0x63, 0x74, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x10, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x50, 0x72, 0x65, 0x64, 0x69, 0x63, 0x74, 0x65, - 0x64, 0x22, 0x47, 0x0a, 0x1c, 0x47, 0x65, 0x74, 0x4c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x46, 0x75, - 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x27, 0x0a, 0x04, 0x72, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x13, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, - 0x44, 0x61, 0x74, 0x61, 0x52, 0x04, 0x72, 0x61, 0x74, 0x65, 0x22, 0x11, 0x0a, 0x0f, 0x53, 0x68, - 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x12, 0x0a, - 0x10, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x22, 0xa1, 0x05, 0x0a, 0x1b, 0x47, 0x65, 0x74, 0x54, 0x65, 0x63, 0x68, 0x6e, 0x69, 0x63, - 0x61, 0x6c, 0x41, 0x6e, 0x61, 0x6c, 0x79, 0x73, 0x69, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x28, 0x0a, - 0x04, 0x70, 0x61, 0x69, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x63, - 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x50, 0x61, 0x69, - 0x72, 0x52, 0x04, 0x70, 0x61, 0x69, 0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x73, 0x73, 0x65, 0x74, - 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x73, 0x73, - 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x6c, 0x67, 0x6f, 0x72, 0x69, - 0x74, 0x68, 0x6d, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, - 0x61, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1a, 0x0a, - 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x30, 0x0a, 0x05, 0x73, 0x74, 0x61, - 0x72, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, - 0x74, 0x61, 0x6d, 0x70, 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x2c, 0x0a, 0x03, 0x65, - 0x6e, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, - 0x74, 0x61, 0x6d, 0x70, 0x52, 0x03, 0x65, 0x6e, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x65, 0x72, - 0x69, 0x6f, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x70, 0x65, 0x72, 0x69, 0x6f, - 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x66, 0x61, 0x73, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x69, 0x6f, 0x64, - 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x66, 0x61, 0x73, 0x74, 0x50, 0x65, 0x72, 0x69, - 0x6f, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x6c, 0x6f, 0x77, 0x5f, 0x70, 0x65, 0x72, 0x69, 0x6f, - 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x73, 0x6c, 0x6f, 0x77, 0x50, 0x65, 0x72, - 0x69, 0x6f, 0x64, 0x12, 0x32, 0x0a, 0x15, 0x73, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x5f, - 0x64, 0x65, 0x76, 0x69, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x75, 0x70, 0x18, 0x0b, 0x20, 0x01, - 0x28, 0x01, 0x52, 0x13, 0x73, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x44, 0x65, 0x76, 0x69, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x70, 0x12, 0x36, 0x0a, 0x17, 0x73, 0x74, 0x61, 0x6e, 0x64, - 0x61, 0x72, 0x64, 0x5f, 0x64, 0x65, 0x76, 0x69, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x64, 0x6f, - 0x77, 0x6e, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x01, 0x52, 0x15, 0x73, 0x74, 0x61, 0x6e, 0x64, 0x61, - 0x72, 0x64, 0x44, 0x65, 0x76, 0x69, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x6f, 0x77, 0x6e, 0x12, - 0x2e, 0x0a, 0x13, 0x6d, 0x6f, 0x76, 0x69, 0x6e, 0x67, 0x5f, 0x61, 0x76, 0x65, 0x72, 0x61, 0x67, - 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x03, 0x52, 0x11, 0x6d, 0x6f, - 0x76, 0x69, 0x6e, 0x67, 0x41, 0x76, 0x65, 0x72, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, - 0x25, 0x0a, 0x0e, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x5f, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, - 0x65, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x45, 0x78, - 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x33, 0x0a, 0x0a, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x5f, - 0x70, 0x61, 0x69, 0x72, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x63, 0x74, + 0x74, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x5f, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x5f, + 0x73, 0x70, 0x6f, 0x74, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x2b, 0x63, 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, 0x43, + 0x6f, 0x6e, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x64, 0x42, 0x79, 0x50, 0x6f, 0x73, 0x69, + 0x74, 0x69, 0x76, 0x65, 0x53, 0x70, 0x6f, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x73, + 0x12, 0x27, 0x0a, 0x0f, 0x75, 0x73, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, + 0x72, 0x61, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x75, 0x73, 0x65, 0x64, 0x43, + 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, 0x12, 0x46, 0x0a, 0x0e, 0x75, 0x73, 0x65, + 0x64, 0x5f, 0x62, 0x72, 0x65, 0x61, 0x6b, 0x64, 0x6f, 0x77, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1f, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6c, 0x6c, 0x61, + 0x74, 0x65, 0x72, 0x61, 0x6c, 0x55, 0x73, 0x65, 0x64, 0x42, 0x72, 0x65, 0x61, 0x6b, 0x64, 0x6f, + 0x77, 0x6e, 0x52, 0x0d, 0x75, 0x73, 0x65, 0x64, 0x42, 0x72, 0x65, 0x61, 0x6b, 0x64, 0x6f, 0x77, + 0x6e, 0x12, 0x31, 0x0a, 0x14, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x63, + 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x13, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x6f, 0x6c, 0x6c, 0x61, 0x74, + 0x65, 0x72, 0x61, 0x6c, 0x12, 0x35, 0x0a, 0x16, 0x6d, 0x61, 0x69, 0x6e, 0x74, 0x65, 0x6e, 0x61, + 0x6e, 0x63, 0x65, 0x5f, 0x63, 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, 0x18, 0x08, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x15, 0x6d, 0x61, 0x69, 0x6e, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x63, + 0x65, 0x43, 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, 0x12, 0x25, 0x0a, 0x0e, 0x75, + 0x6e, 0x72, 0x65, 0x61, 0x6c, 0x69, 0x73, 0x65, 0x64, 0x5f, 0x70, 0x6e, 0x6c, 0x18, 0x09, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0d, 0x75, 0x6e, 0x72, 0x65, 0x61, 0x6c, 0x69, 0x73, 0x65, 0x64, 0x50, + 0x6e, 0x6c, 0x12, 0x4c, 0x0a, 0x12, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x62, + 0x72, 0x65, 0x61, 0x6b, 0x64, 0x6f, 0x77, 0x6e, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, + 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, 0x72, + 0x61, 0x6c, 0x46, 0x6f, 0x72, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x11, 0x63, + 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x42, 0x72, 0x65, 0x61, 0x6b, 0x64, 0x6f, 0x77, 0x6e, + 0x12, 0x4b, 0x0a, 0x12, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x62, 0x72, 0x65, + 0x61, 0x6b, 0x64, 0x6f, 0x77, 0x6e, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, + 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, + 0x42, 0x79, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x11, 0x70, 0x6f, 0x73, 0x69, + 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x72, 0x65, 0x61, 0x6b, 0x64, 0x6f, 0x77, 0x6e, 0x22, 0xf7, 0x04, + 0x0a, 0x15, 0x43, 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, 0x46, 0x6f, 0x72, 0x43, + 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x75, 0x72, 0x72, 0x65, + 0x6e, 0x63, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x75, 0x72, 0x72, 0x65, + 0x6e, 0x63, 0x79, 0x12, 0x38, 0x0a, 0x18, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64, 0x5f, + 0x66, 0x72, 0x6f, 0x6d, 0x5f, 0x63, 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x16, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64, 0x46, + 0x72, 0x6f, 0x6d, 0x43, 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, 0x12, 0x1f, 0x0a, + 0x0b, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x66, 0x75, 0x6e, 0x64, 0x73, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x46, 0x75, 0x6e, 0x64, 0x73, 0x12, 0x44, + 0x0a, 0x1f, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x66, 0x6f, 0x72, 0x5f, + 0x75, 0x73, 0x65, 0x5f, 0x61, 0x73, 0x5f, 0x63, 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, + 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x1b, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, + 0x6c, 0x65, 0x46, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x41, 0x73, 0x43, 0x6f, 0x6c, 0x6c, 0x61, 0x74, + 0x65, 0x72, 0x61, 0x6c, 0x12, 0x37, 0x0a, 0x18, 0x61, 0x70, 0x70, 0x72, 0x6f, 0x78, 0x5f, 0x66, + 0x61, 0x69, 0x72, 0x5f, 0x6d, 0x61, 0x72, 0x6b, 0x65, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x15, 0x61, 0x70, 0x70, 0x72, 0x6f, 0x78, 0x46, 0x61, + 0x69, 0x72, 0x4d, 0x61, 0x72, 0x6b, 0x65, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x1c, 0x0a, + 0x09, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x69, 0x6e, 0x67, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x09, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x69, 0x6e, 0x67, 0x12, 0x37, 0x0a, 0x17, 0x63, + 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x69, + 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x16, 0x63, 0x6f, + 0x6c, 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x69, 0x62, 0x75, + 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2c, 0x0a, 0x12, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x64, 0x5f, 0x74, + 0x6f, 0x5f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x10, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x64, 0x54, 0x6f, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, + 0x63, 0x79, 0x12, 0x25, 0x0a, 0x0e, 0x75, 0x6e, 0x72, 0x65, 0x61, 0x6c, 0x69, 0x73, 0x65, 0x64, + 0x5f, 0x70, 0x6e, 0x6c, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x75, 0x6e, 0x72, 0x65, + 0x61, 0x6c, 0x69, 0x73, 0x65, 0x64, 0x50, 0x6e, 0x6c, 0x12, 0x20, 0x0a, 0x0c, 0x66, 0x75, 0x6e, + 0x64, 0x73, 0x5f, 0x69, 0x6e, 0x5f, 0x75, 0x73, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0a, 0x66, 0x75, 0x6e, 0x64, 0x73, 0x49, 0x6e, 0x55, 0x73, 0x65, 0x12, 0x3c, 0x0a, 0x1a, 0x61, + 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x5f, 0x63, 0x6f, 0x6c, 0x6c, 0x61, 0x74, + 0x65, 0x72, 0x61, 0x6c, 0x5f, 0x75, 0x73, 0x65, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x18, 0x61, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x43, 0x6f, 0x6c, 0x6c, 0x61, + 0x74, 0x65, 0x72, 0x61, 0x6c, 0x55, 0x73, 0x65, 0x64, 0x12, 0x46, 0x0a, 0x0e, 0x75, 0x73, 0x65, + 0x64, 0x5f, 0x62, 0x72, 0x65, 0x61, 0x6b, 0x64, 0x6f, 0x77, 0x6e, 0x18, 0x0c, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1f, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6c, 0x6c, 0x61, + 0x74, 0x65, 0x72, 0x61, 0x6c, 0x55, 0x73, 0x65, 0x64, 0x42, 0x72, 0x65, 0x61, 0x6b, 0x64, 0x6f, + 0x77, 0x6e, 0x52, 0x0d, 0x75, 0x73, 0x65, 0x64, 0x42, 0x72, 0x65, 0x61, 0x6b, 0x64, 0x6f, 0x77, + 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x8f, 0x02, 0x0a, 0x14, 0x43, 0x6f, 0x6c, 0x6c, + 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, 0x42, 0x79, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, + 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x08, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x12, 0x0a, 0x04, + 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, + 0x12, 0x26, 0x0a, 0x0f, 0x6f, 0x70, 0x65, 0x6e, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x73, + 0x69, 0x7a, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6f, 0x70, 0x65, 0x6e, 0x4f, + 0x72, 0x64, 0x65, 0x72, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x6f, 0x73, 0x69, + 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0c, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1d, 0x0a, + 0x0a, 0x6d, 0x61, 0x72, 0x6b, 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x09, 0x6d, 0x61, 0x72, 0x6b, 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, 0x27, 0x0a, 0x0f, + 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x5f, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x4d, + 0x61, 0x72, 0x67, 0x69, 0x6e, 0x12, 0x32, 0x0a, 0x15, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x63, + 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, 0x5f, 0x75, 0x73, 0x65, 0x64, 0x18, 0x07, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x43, 0x6f, 0x6c, 0x6c, 0x61, + 0x74, 0x65, 0x72, 0x61, 0x6c, 0x55, 0x73, 0x65, 0x64, 0x22, 0xae, 0x03, 0x0a, 0x17, 0x43, 0x6f, + 0x6c, 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, 0x55, 0x73, 0x65, 0x64, 0x42, 0x72, 0x65, 0x61, + 0x6b, 0x64, 0x6f, 0x77, 0x6e, 0x12, 0x28, 0x0a, 0x10, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x5f, + 0x69, 0x6e, 0x5f, 0x73, 0x74, 0x61, 0x6b, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0e, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x49, 0x6e, 0x53, 0x74, 0x61, 0x6b, 0x65, 0x73, 0x12, + 0x2b, 0x0a, 0x12, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x5f, 0x69, 0x6e, 0x5f, 0x6e, 0x66, 0x74, + 0x5f, 0x62, 0x69, 0x64, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x6c, 0x6f, 0x63, + 0x6b, 0x65, 0x64, 0x49, 0x6e, 0x4e, 0x66, 0x74, 0x42, 0x69, 0x64, 0x73, 0x12, 0x31, 0x0a, 0x15, + 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x5f, 0x69, 0x6e, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x76, 0x6f, + 0x75, 0x63, 0x68, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x6c, 0x6f, 0x63, + 0x6b, 0x65, 0x64, 0x49, 0x6e, 0x46, 0x65, 0x65, 0x56, 0x6f, 0x75, 0x63, 0x68, 0x65, 0x72, 0x12, + 0x4d, 0x0a, 0x24, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x5f, 0x69, 0x6e, 0x5f, 0x73, 0x70, 0x6f, + 0x74, 0x5f, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x5f, 0x66, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, + 0x5f, 0x6f, 0x66, 0x66, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x1f, 0x6c, + 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x49, 0x6e, 0x53, 0x70, 0x6f, 0x74, 0x4d, 0x61, 0x72, 0x67, 0x69, + 0x6e, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x4f, 0x66, 0x66, 0x65, 0x72, 0x73, 0x12, 0x31, + 0x0a, 0x15, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x5f, 0x69, 0x6e, 0x5f, 0x73, 0x70, 0x6f, 0x74, + 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x6c, + 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x49, 0x6e, 0x53, 0x70, 0x6f, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, + 0x73, 0x12, 0x30, 0x0a, 0x14, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x5f, 0x61, 0x73, 0x5f, 0x63, + 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x12, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x41, 0x73, 0x43, 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, + 0x72, 0x61, 0x6c, 0x12, 0x26, 0x0a, 0x0f, 0x75, 0x73, 0x65, 0x64, 0x5f, 0x69, 0x6e, 0x5f, 0x66, + 0x75, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x75, 0x73, + 0x65, 0x64, 0x49, 0x6e, 0x46, 0x75, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x2d, 0x0a, 0x13, 0x75, + 0x73, 0x65, 0x64, 0x5f, 0x69, 0x6e, 0x5f, 0x73, 0x70, 0x6f, 0x74, 0x5f, 0x6d, 0x61, 0x72, 0x67, + 0x69, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x75, 0x73, 0x65, 0x64, 0x49, 0x6e, + 0x53, 0x70, 0x6f, 0x74, 0x4d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x22, 0xe7, 0x02, 0x0a, 0x16, 0x47, + 0x65, 0x74, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x61, 0x74, 0x65, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, + 0x65, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x12, 0x28, 0x0a, 0x04, 0x70, 0x61, 0x69, 0x72, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, + 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x50, 0x61, 0x69, 0x72, 0x52, 0x04, 0x70, 0x61, 0x69, + 0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x44, 0x61, 0x74, 0x65, + 0x12, 0x19, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x07, 0x65, 0x6e, 0x64, 0x44, 0x61, 0x74, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x70, + 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x43, 0x75, + 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x2b, 0x0a, 0x11, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, + 0x65, 0x5f, 0x70, 0x72, 0x65, 0x64, 0x69, 0x63, 0x74, 0x65, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x10, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x50, 0x72, 0x65, 0x64, 0x69, 0x63, + 0x74, 0x65, 0x64, 0x12, 0x29, 0x0a, 0x10, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x70, + 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x69, + 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x34, + 0x0a, 0x16, 0x72, 0x65, 0x73, 0x70, 0x65, 0x63, 0x74, 0x5f, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, + 0x79, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x14, + 0x72, 0x65, 0x73, 0x70, 0x65, 0x63, 0x74, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x4c, 0x69, + 0x6d, 0x69, 0x74, 0x73, 0x22, 0x44, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x46, 0x75, 0x6e, 0x64, 0x69, + 0x6e, 0x67, 0x52, 0x61, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x29, 0x0a, 0x05, 0x72, 0x61, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, + 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x44, + 0x61, 0x74, 0x61, 0x52, 0x05, 0x72, 0x61, 0x74, 0x65, 0x73, 0x22, 0xa6, 0x01, 0x0a, 0x1b, 0x47, + 0x65, 0x74, 0x4c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x52, + 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, + 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, + 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x12, 0x28, 0x0a, 0x04, + 0x70, 0x61, 0x69, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x50, 0x61, 0x69, 0x72, - 0x52, 0x09, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x50, 0x61, 0x69, 0x72, 0x12, 0x28, 0x0a, 0x10, 0x6f, - 0x74, 0x68, 0x65, 0x72, 0x5f, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, - 0x10, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x41, 0x73, 0x73, 0x65, - 0x74, 0x54, 0x79, 0x70, 0x65, 0x22, 0x29, 0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x66, 0x53, - 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, - 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x01, 0x52, 0x07, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x73, - 0x22, 0xbe, 0x01, 0x0a, 0x1c, 0x47, 0x65, 0x74, 0x54, 0x65, 0x63, 0x68, 0x6e, 0x69, 0x63, 0x61, - 0x6c, 0x41, 0x6e, 0x61, 0x6c, 0x79, 0x73, 0x69, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x4b, 0x0a, 0x07, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x31, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x54, - 0x65, 0x63, 0x68, 0x6e, 0x69, 0x63, 0x61, 0x6c, 0x41, 0x6e, 0x61, 0x6c, 0x79, 0x73, 0x69, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x73, - 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x73, 0x1a, 0x51, - 0x0a, 0x0c, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, - 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, - 0x12, 0x2b, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x15, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x66, 0x53, - 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x73, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, - 0x01, 0x22, 0x83, 0x04, 0x0a, 0x1c, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x52, - 0x61, 0x74, 0x65, 0x73, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x14, - 0x0a, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, - 0x73, 0x73, 0x65, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, - 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x44, 0x61, 0x74, 0x65, 0x12, - 0x19, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x07, 0x65, 0x6e, 0x64, 0x44, 0x61, 0x74, 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x67, 0x65, - 0x74, 0x5f, 0x70, 0x72, 0x65, 0x64, 0x69, 0x63, 0x74, 0x65, 0x64, 0x5f, 0x72, 0x61, 0x74, 0x65, - 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x67, 0x65, 0x74, 0x50, 0x72, 0x65, 0x64, 0x69, - 0x63, 0x74, 0x65, 0x64, 0x52, 0x61, 0x74, 0x65, 0x12, 0x30, 0x0a, 0x14, 0x67, 0x65, 0x74, 0x5f, - 0x6c, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, - 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x67, 0x65, 0x74, 0x4c, 0x65, 0x6e, 0x64, 0x69, - 0x6e, 0x67, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x28, 0x0a, 0x10, 0x67, 0x65, - 0x74, 0x5f, 0x62, 0x6f, 0x72, 0x72, 0x6f, 0x77, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x73, 0x18, 0x08, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x67, 0x65, 0x74, 0x42, 0x6f, 0x72, 0x72, 0x6f, 0x77, 0x52, - 0x61, 0x74, 0x65, 0x73, 0x12, 0x28, 0x0a, 0x10, 0x67, 0x65, 0x74, 0x5f, 0x62, 0x6f, 0x72, 0x72, - 0x6f, 0x77, 0x5f, 0x63, 0x6f, 0x73, 0x74, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, - 0x67, 0x65, 0x74, 0x42, 0x6f, 0x72, 0x72, 0x6f, 0x77, 0x43, 0x6f, 0x73, 0x74, 0x73, 0x12, 0x2a, - 0x0a, 0x11, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x61, 0x6c, 0x6c, 0x5f, 0x72, 0x61, - 0x74, 0x65, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x69, 0x6e, 0x63, 0x6c, 0x75, - 0x64, 0x65, 0x41, 0x6c, 0x6c, 0x52, 0x61, 0x74, 0x65, 0x73, 0x12, 0x2b, 0x0a, 0x11, 0x63, 0x61, - 0x6c, 0x63, 0x75, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x6f, 0x66, 0x66, 0x6c, 0x69, 0x6e, 0x65, 0x18, - 0x0b, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x63, 0x61, 0x6c, 0x63, 0x75, 0x6c, 0x61, 0x74, 0x65, - 0x4f, 0x66, 0x66, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x6b, 0x65, 0x72, - 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0c, 0x74, 0x61, 0x6b, 0x65, 0x72, 0x46, 0x65, 0x65, 0x52, 0x61, 0x74, 0x65, 0x12, 0x28, 0x0a, - 0x05, 0x72, 0x61, 0x74, 0x65, 0x73, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x67, - 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x52, 0x61, 0x74, 0x65, - 0x52, 0x05, 0x72, 0x61, 0x74, 0x65, 0x73, 0x22, 0x3e, 0x0a, 0x0e, 0x4c, 0x65, 0x6e, 0x64, 0x69, - 0x6e, 0x67, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x79, - 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x61, 0x79, 0x6d, - 0x65, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x22, 0x34, 0x0a, 0x0a, 0x42, 0x6f, 0x72, 0x72, 0x6f, - 0x77, 0x43, 0x6f, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x73, 0x74, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x63, 0x6f, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x22, 0xe2, 0x02, - 0x0a, 0x0a, 0x4d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x52, 0x61, 0x74, 0x65, 0x12, 0x12, 0x0a, 0x04, - 0x74, 0x69, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x69, 0x6d, 0x65, - 0x12, 0x2c, 0x0a, 0x12, 0x6d, 0x61, 0x72, 0x6b, 0x65, 0x74, 0x5f, 0x62, 0x6f, 0x72, 0x72, 0x6f, - 0x77, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x6d, 0x61, - 0x72, 0x6b, 0x65, 0x74, 0x42, 0x6f, 0x72, 0x72, 0x6f, 0x77, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1f, - 0x0a, 0x0b, 0x68, 0x6f, 0x75, 0x72, 0x6c, 0x79, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0a, 0x68, 0x6f, 0x75, 0x72, 0x6c, 0x79, 0x52, 0x61, 0x74, 0x65, 0x12, - 0x1f, 0x0a, 0x0b, 0x79, 0x65, 0x61, 0x72, 0x6c, 0x79, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x79, 0x65, 0x61, 0x72, 0x6c, 0x79, 0x52, 0x61, 0x74, 0x65, - 0x12, 0x2c, 0x0a, 0x12, 0x68, 0x6f, 0x75, 0x72, 0x6c, 0x79, 0x5f, 0x62, 0x6f, 0x72, 0x72, 0x6f, - 0x77, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x68, 0x6f, - 0x75, 0x72, 0x6c, 0x79, 0x42, 0x6f, 0x72, 0x72, 0x6f, 0x77, 0x52, 0x61, 0x74, 0x65, 0x12, 0x2c, - 0x0a, 0x12, 0x79, 0x65, 0x61, 0x72, 0x6c, 0x79, 0x5f, 0x62, 0x6f, 0x72, 0x72, 0x6f, 0x77, 0x5f, - 0x72, 0x61, 0x74, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x79, 0x65, 0x61, 0x72, - 0x6c, 0x79, 0x42, 0x6f, 0x72, 0x72, 0x6f, 0x77, 0x52, 0x61, 0x74, 0x65, 0x12, 0x3f, 0x0a, 0x0f, - 0x6c, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x18, - 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, - 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x0e, 0x6c, - 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x33, 0x0a, - 0x0b, 0x62, 0x6f, 0x72, 0x72, 0x6f, 0x77, 0x5f, 0x63, 0x6f, 0x73, 0x74, 0x18, 0x08, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x6f, 0x72, 0x72, - 0x6f, 0x77, 0x43, 0x6f, 0x73, 0x74, 0x52, 0x0a, 0x62, 0x6f, 0x72, 0x72, 0x6f, 0x77, 0x43, 0x6f, - 0x73, 0x74, 0x22, 0xae, 0x03, 0x0a, 0x1d, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x72, 0x67, 0x69, 0x6e, - 0x52, 0x61, 0x74, 0x65, 0x73, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x28, 0x0a, 0x05, 0x72, 0x61, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x61, 0x72, - 0x67, 0x69, 0x6e, 0x52, 0x61, 0x74, 0x65, 0x52, 0x05, 0x72, 0x61, 0x74, 0x65, 0x73, 0x12, 0x1f, - 0x0a, 0x0b, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x73, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x52, 0x61, 0x74, 0x65, 0x73, 0x12, - 0x28, 0x0a, 0x10, 0x73, 0x75, 0x6d, 0x5f, 0x62, 0x6f, 0x72, 0x72, 0x6f, 0x77, 0x5f, 0x63, 0x6f, - 0x73, 0x74, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x73, 0x75, 0x6d, 0x42, 0x6f, - 0x72, 0x72, 0x6f, 0x77, 0x43, 0x6f, 0x73, 0x74, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x61, 0x76, 0x67, - 0x5f, 0x62, 0x6f, 0x72, 0x72, 0x6f, 0x77, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0d, 0x61, 0x76, 0x67, 0x42, 0x6f, 0x72, 0x72, 0x6f, 0x77, 0x53, 0x69, 0x7a, - 0x65, 0x12, 0x30, 0x0a, 0x14, 0x73, 0x75, 0x6d, 0x5f, 0x6c, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, - 0x5f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x12, 0x73, 0x75, 0x6d, 0x4c, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x50, 0x61, 0x79, 0x6d, 0x65, - 0x6e, 0x74, 0x73, 0x12, 0x28, 0x0a, 0x10, 0x61, 0x76, 0x67, 0x5f, 0x6c, 0x65, 0x6e, 0x64, 0x69, - 0x6e, 0x67, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x61, - 0x76, 0x67, 0x4c, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x33, 0x0a, - 0x0b, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, 0x07, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x61, 0x72, 0x67, - 0x69, 0x6e, 0x52, 0x61, 0x74, 0x65, 0x52, 0x0a, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x52, 0x61, - 0x74, 0x65, 0x12, 0x39, 0x0a, 0x0e, 0x70, 0x72, 0x65, 0x64, 0x69, 0x63, 0x74, 0x65, 0x64, 0x5f, - 0x72, 0x61, 0x74, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x67, 0x63, 0x74, - 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x52, 0x61, 0x74, 0x65, 0x52, 0x0d, - 0x70, 0x72, 0x65, 0x64, 0x69, 0x63, 0x74, 0x65, 0x64, 0x52, 0x61, 0x74, 0x65, 0x12, 0x24, 0x0a, - 0x0e, 0x74, 0x61, 0x6b, 0x65, 0x72, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, - 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x74, 0x61, 0x6b, 0x65, 0x72, 0x46, 0x65, 0x65, 0x52, - 0x61, 0x74, 0x65, 0x22, 0xf9, 0x01, 0x0a, 0x1b, 0x47, 0x65, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, - 0x62, 0x6f, 0x6f, 0x6b, 0x4d, 0x6f, 0x76, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, + 0x52, 0x04, 0x70, 0x61, 0x69, 0x72, 0x12, 0x2b, 0x0a, 0x11, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, + 0x65, 0x5f, 0x70, 0x72, 0x65, 0x64, 0x69, 0x63, 0x74, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x10, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x50, 0x72, 0x65, 0x64, 0x69, 0x63, + 0x74, 0x65, 0x64, 0x22, 0x47, 0x0a, 0x1c, 0x47, 0x65, 0x74, 0x4c, 0x61, 0x74, 0x65, 0x73, 0x74, + 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x27, 0x0a, 0x04, 0x72, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x13, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, 0x6e, 0x64, 0x69, + 0x6e, 0x67, 0x44, 0x61, 0x74, 0x61, 0x52, 0x04, 0x72, 0x61, 0x74, 0x65, 0x22, 0x11, 0x0a, 0x0f, + 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, + 0x12, 0x0a, 0x10, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0xa1, 0x05, 0x0a, 0x1b, 0x47, 0x65, 0x74, 0x54, 0x65, 0x63, 0x68, 0x6e, + 0x69, 0x63, 0x61, 0x6c, 0x41, 0x6e, 0x61, 0x6c, 0x79, 0x73, 0x69, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, - 0x14, 0x0a, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, - 0x61, 0x73, 0x73, 0x65, 0x74, 0x12, 0x28, 0x0a, 0x04, 0x70, 0x61, 0x69, 0x72, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x75, 0x72, - 0x72, 0x65, 0x6e, 0x63, 0x79, 0x50, 0x61, 0x69, 0x72, 0x52, 0x04, 0x70, 0x61, 0x69, 0x72, 0x12, - 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x01, 0x52, - 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x65, 0x6c, 0x6c, 0x18, - 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x73, 0x65, 0x6c, 0x6c, 0x12, 0x36, 0x0a, 0x17, 0x72, - 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x73, 0x5f, 0x72, 0x65, 0x73, 0x74, 0x5f, 0x6f, 0x72, 0x64, - 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x15, 0x72, 0x65, - 0x71, 0x75, 0x69, 0x72, 0x65, 0x73, 0x52, 0x65, 0x73, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x62, - 0x6f, 0x6f, 0x6b, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x75, 0x72, 0x63, 0x68, 0x61, 0x73, 0x65, 0x18, - 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x70, 0x75, 0x72, 0x63, 0x68, 0x61, 0x73, 0x65, 0x22, - 0xc6, 0x04, 0x0a, 0x1c, 0x47, 0x65, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, - 0x4d, 0x6f, 0x76, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x2d, 0x0a, 0x12, 0x6e, 0x6f, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x70, 0x65, 0x72, 0x63, - 0x65, 0x6e, 0x74, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x01, 0x52, 0x11, 0x6e, 0x6f, - 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x50, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x61, 0x67, 0x65, 0x12, - 0x2b, 0x0a, 0x11, 0x69, 0x6d, 0x70, 0x61, 0x63, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x63, 0x65, 0x6e, - 0x74, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x10, 0x69, 0x6d, 0x70, 0x61, - 0x63, 0x74, 0x50, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x61, 0x67, 0x65, 0x12, 0x23, 0x0a, 0x0d, - 0x73, 0x6c, 0x69, 0x70, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x73, 0x74, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x01, 0x52, 0x0c, 0x73, 0x6c, 0x69, 0x70, 0x70, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x73, - 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x62, 0x6f, - 0x75, 0x67, 0x68, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x63, 0x75, 0x72, 0x72, - 0x65, 0x6e, 0x63, 0x79, 0x42, 0x6f, 0x75, 0x67, 0x68, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x62, 0x6f, - 0x75, 0x67, 0x68, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x01, 0x52, 0x06, 0x62, 0x6f, 0x75, 0x67, - 0x68, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x73, - 0x6f, 0x6c, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x75, 0x72, 0x72, 0x65, - 0x6e, 0x63, 0x79, 0x53, 0x6f, 0x6c, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x6f, 0x6c, 0x64, 0x18, - 0x07, 0x20, 0x01, 0x28, 0x01, 0x52, 0x04, 0x73, 0x6f, 0x6c, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x73, - 0x69, 0x64, 0x65, 0x5f, 0x61, 0x66, 0x66, 0x65, 0x63, 0x74, 0x65, 0x64, 0x18, 0x08, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0c, 0x73, 0x69, 0x64, 0x65, 0x41, 0x66, 0x66, 0x65, 0x63, 0x74, 0x65, 0x64, - 0x12, 0x27, 0x0a, 0x0f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x63, 0x6f, 0x6c, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x75, 0x70, 0x64, 0x61, 0x74, - 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x3f, 0x0a, 0x1c, 0x66, 0x75, 0x6c, - 0x6c, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x5f, 0x73, 0x69, 0x64, 0x65, - 0x5f, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x19, 0x66, 0x75, 0x6c, 0x6c, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x53, 0x69, - 0x64, 0x65, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x74, - 0x61, 0x72, 0x74, 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x01, 0x52, - 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x65, - 0x6e, 0x64, 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x01, 0x52, 0x08, - 0x65, 0x6e, 0x64, 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, 0x30, 0x0a, 0x14, 0x6e, 0x6f, 0x5f, 0x73, - 0x6c, 0x69, 0x70, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x6f, 0x63, 0x63, 0x75, 0x72, 0x72, 0x65, 0x64, - 0x18, 0x0d, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x6e, 0x6f, 0x53, 0x6c, 0x69, 0x70, 0x70, 0x61, - 0x67, 0x65, 0x4f, 0x63, 0x63, 0x75, 0x72, 0x72, 0x65, 0x64, 0x12, 0x2c, 0x0a, 0x12, 0x61, 0x76, - 0x65, 0x72, 0x61, 0x67, 0x65, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x73, 0x74, - 0x18, 0x0e, 0x20, 0x01, 0x28, 0x01, 0x52, 0x10, 0x61, 0x76, 0x65, 0x72, 0x61, 0x67, 0x65, 0x4f, - 0x72, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x73, 0x74, 0x22, 0xfb, 0x01, 0x0a, 0x22, 0x47, 0x65, 0x74, - 0x4f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x42, - 0x79, 0x4e, 0x6f, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x61, - 0x73, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x73, 0x73, 0x65, - 0x74, 0x12, 0x28, 0x0a, 0x04, 0x70, 0x61, 0x69, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x14, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, - 0x79, 0x50, 0x61, 0x69, 0x72, 0x52, 0x04, 0x70, 0x61, 0x69, 0x72, 0x12, 0x2d, 0x0a, 0x12, 0x6e, - 0x6f, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x70, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x61, 0x67, - 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x01, 0x52, 0x11, 0x6e, 0x6f, 0x6d, 0x69, 0x6e, 0x61, 0x6c, - 0x50, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x61, 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x65, - 0x6c, 0x6c, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x73, 0x65, 0x6c, 0x6c, 0x12, 0x36, - 0x0a, 0x17, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x73, 0x5f, 0x72, 0x65, 0x73, 0x74, 0x5f, - 0x6f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x15, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x73, 0x52, 0x65, 0x73, 0x74, 0x4f, 0x72, 0x64, - 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x22, 0x9d, 0x04, 0x0a, 0x23, 0x47, 0x65, 0x74, 0x4f, 0x72, - 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x79, 0x4e, - 0x6f, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x27, - 0x0a, 0x0f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0e, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x52, - 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x12, 0x29, 0x0a, 0x10, 0x63, 0x75, 0x72, 0x72, 0x65, - 0x6e, 0x63, 0x79, 0x5f, 0x73, 0x65, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x53, 0x65, 0x6c, 0x6c, 0x69, - 0x6e, 0x67, 0x12, 0x27, 0x0a, 0x0f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x72, 0x65, 0x63, - 0x65, 0x69, 0x76, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0e, 0x61, 0x6d, 0x6f, - 0x75, 0x6e, 0x74, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x64, 0x12, 0x27, 0x0a, 0x0f, 0x63, - 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x62, 0x75, 0x79, 0x69, 0x6e, 0x67, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x42, 0x75, - 0x79, 0x69, 0x6e, 0x67, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x70, 0x72, - 0x69, 0x63, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, - 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x65, 0x6e, 0x64, 0x5f, 0x70, 0x72, 0x69, - 0x63, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x01, 0x52, 0x08, 0x65, 0x6e, 0x64, 0x50, 0x72, 0x69, - 0x63, 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x61, 0x76, 0x65, 0x72, 0x61, 0x67, 0x65, 0x5f, 0x6f, 0x72, - 0x64, 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x73, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x01, 0x52, 0x10, - 0x61, 0x76, 0x65, 0x72, 0x61, 0x67, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x73, 0x74, - 0x12, 0x23, 0x0a, 0x0d, 0x73, 0x69, 0x64, 0x65, 0x5f, 0x61, 0x66, 0x66, 0x65, 0x63, 0x74, 0x65, - 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x73, 0x69, 0x64, 0x65, 0x41, 0x66, 0x66, - 0x65, 0x63, 0x74, 0x65, 0x64, 0x12, 0x55, 0x0a, 0x27, 0x61, 0x70, 0x70, 0x72, 0x6f, 0x78, 0x69, - 0x6d, 0x61, 0x74, 0x65, 0x5f, 0x6e, 0x6f, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x73, 0x6c, 0x69, - 0x70, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x70, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x61, 0x67, 0x65, - 0x18, 0x09, 0x20, 0x01, 0x28, 0x01, 0x52, 0x24, 0x61, 0x70, 0x70, 0x72, 0x6f, 0x78, 0x69, 0x6d, - 0x61, 0x74, 0x65, 0x4e, 0x6f, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x53, 0x6c, 0x69, 0x70, 0x70, 0x61, - 0x67, 0x65, 0x50, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x61, 0x67, 0x65, 0x12, 0x27, 0x0a, 0x0f, - 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, - 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x72, 0x6f, - 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x3f, 0x0a, 0x1c, 0x66, 0x75, 0x6c, 0x6c, 0x5f, 0x6f, 0x72, - 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x5f, 0x73, 0x69, 0x64, 0x65, 0x5f, 0x63, 0x6f, 0x6e, - 0x73, 0x75, 0x6d, 0x65, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x08, 0x52, 0x19, 0x66, 0x75, 0x6c, - 0x6c, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x53, 0x69, 0x64, 0x65, 0x43, 0x6f, - 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x64, 0x22, 0xf8, 0x01, 0x0a, 0x21, 0x47, 0x65, 0x74, 0x4f, 0x72, - 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x79, 0x49, - 0x6d, 0x70, 0x61, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, - 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, - 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x73, 0x73, 0x65, - 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x12, 0x28, - 0x0a, 0x04, 0x70, 0x61, 0x69, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, + 0x28, 0x0a, 0x04, 0x70, 0x61, 0x69, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, + 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x50, + 0x61, 0x69, 0x72, 0x52, 0x04, 0x70, 0x61, 0x69, 0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x73, 0x73, + 0x65, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, + 0x73, 0x73, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x6c, 0x67, 0x6f, + 0x72, 0x69, 0x74, 0x68, 0x6d, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0d, 0x61, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x54, 0x79, 0x70, 0x65, 0x12, + 0x1a, 0x0a, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x30, 0x0a, 0x05, 0x73, + 0x74, 0x61, 0x72, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, + 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x2c, 0x0a, + 0x03, 0x65, 0x6e, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, + 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x03, 0x65, 0x6e, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x70, + 0x65, 0x72, 0x69, 0x6f, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x70, 0x65, 0x72, + 0x69, 0x6f, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x66, 0x61, 0x73, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x69, + 0x6f, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x66, 0x61, 0x73, 0x74, 0x50, 0x65, + 0x72, 0x69, 0x6f, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x6c, 0x6f, 0x77, 0x5f, 0x70, 0x65, 0x72, + 0x69, 0x6f, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x73, 0x6c, 0x6f, 0x77, 0x50, + 0x65, 0x72, 0x69, 0x6f, 0x64, 0x12, 0x32, 0x0a, 0x15, 0x73, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, + 0x64, 0x5f, 0x64, 0x65, 0x76, 0x69, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x75, 0x70, 0x18, 0x0b, + 0x20, 0x01, 0x28, 0x01, 0x52, 0x13, 0x73, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x44, 0x65, + 0x76, 0x69, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x70, 0x12, 0x36, 0x0a, 0x17, 0x73, 0x74, 0x61, + 0x6e, 0x64, 0x61, 0x72, 0x64, 0x5f, 0x64, 0x65, 0x76, 0x69, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, + 0x64, 0x6f, 0x77, 0x6e, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x01, 0x52, 0x15, 0x73, 0x74, 0x61, 0x6e, + 0x64, 0x61, 0x72, 0x64, 0x44, 0x65, 0x76, 0x69, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x6f, 0x77, + 0x6e, 0x12, 0x2e, 0x0a, 0x13, 0x6d, 0x6f, 0x76, 0x69, 0x6e, 0x67, 0x5f, 0x61, 0x76, 0x65, 0x72, + 0x61, 0x67, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x03, 0x52, 0x11, + 0x6d, 0x6f, 0x76, 0x69, 0x6e, 0x67, 0x41, 0x76, 0x65, 0x72, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, + 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x5f, 0x65, 0x78, 0x63, 0x68, 0x61, + 0x6e, 0x67, 0x65, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6f, 0x74, 0x68, 0x65, 0x72, + 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x33, 0x0a, 0x0a, 0x6f, 0x74, 0x68, 0x65, + 0x72, 0x5f, 0x70, 0x61, 0x69, 0x72, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x50, 0x61, - 0x69, 0x72, 0x52, 0x04, 0x70, 0x61, 0x69, 0x72, 0x12, 0x2b, 0x0a, 0x11, 0x69, 0x6d, 0x70, 0x61, - 0x63, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x61, 0x67, 0x65, 0x18, 0x06, 0x20, - 0x01, 0x28, 0x01, 0x52, 0x10, 0x69, 0x6d, 0x70, 0x61, 0x63, 0x74, 0x50, 0x65, 0x72, 0x63, 0x65, - 0x6e, 0x74, 0x61, 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x65, 0x6c, 0x6c, 0x18, 0x07, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x04, 0x73, 0x65, 0x6c, 0x6c, 0x12, 0x36, 0x0a, 0x17, 0x72, 0x65, 0x71, - 0x75, 0x69, 0x72, 0x65, 0x73, 0x5f, 0x72, 0x65, 0x73, 0x74, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, - 0x62, 0x6f, 0x6f, 0x6b, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x15, 0x72, 0x65, 0x71, 0x75, - 0x69, 0x72, 0x65, 0x73, 0x52, 0x65, 0x73, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, - 0x6b, 0x22, 0x9a, 0x04, 0x0a, 0x22, 0x47, 0x65, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, - 0x6f, 0x6b, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x79, 0x49, 0x6d, 0x70, 0x61, 0x63, 0x74, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x61, 0x6d, 0x6f, 0x75, - 0x6e, 0x74, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x01, 0x52, 0x0e, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, - 0x64, 0x12, 0x29, 0x0a, 0x10, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x73, 0x65, - 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x63, 0x75, 0x72, - 0x72, 0x65, 0x6e, 0x63, 0x79, 0x53, 0x65, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x12, 0x27, 0x0a, 0x0f, - 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x64, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0e, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x63, - 0x65, 0x69, 0x76, 0x65, 0x64, 0x12, 0x27, 0x0a, 0x0f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, - 0x79, 0x5f, 0x62, 0x75, 0x79, 0x69, 0x6e, 0x67, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, - 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x42, 0x75, 0x79, 0x69, 0x6e, 0x67, 0x12, 0x1f, - 0x0a, 0x0b, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x01, 0x52, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, - 0x1b, 0x0a, 0x09, 0x65, 0x6e, 0x64, 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x06, 0x20, 0x01, - 0x28, 0x01, 0x52, 0x08, 0x65, 0x6e, 0x64, 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, 0x2c, 0x0a, 0x12, + 0x69, 0x72, 0x52, 0x09, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x50, 0x61, 0x69, 0x72, 0x12, 0x28, 0x0a, + 0x10, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x5f, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x74, 0x79, 0x70, + 0x65, 0x18, 0x10, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x41, 0x73, + 0x73, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x22, 0x29, 0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74, 0x4f, + 0x66, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x69, 0x67, 0x6e, + 0x61, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x01, 0x52, 0x07, 0x73, 0x69, 0x67, 0x6e, 0x61, + 0x6c, 0x73, 0x22, 0xbe, 0x01, 0x0a, 0x1c, 0x47, 0x65, 0x74, 0x54, 0x65, 0x63, 0x68, 0x6e, 0x69, + 0x63, 0x61, 0x6c, 0x41, 0x6e, 0x61, 0x6c, 0x79, 0x73, 0x69, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x07, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x73, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x31, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, + 0x74, 0x54, 0x65, 0x63, 0x68, 0x6e, 0x69, 0x63, 0x61, 0x6c, 0x41, 0x6e, 0x61, 0x6c, 0x79, 0x73, + 0x69, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x61, + 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x73, + 0x1a, 0x51, 0x0a, 0x0c, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, + 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, + 0x65, 0x79, 0x12, 0x2b, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x15, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4f, + 0x66, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x73, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, + 0x02, 0x38, 0x01, 0x22, 0x83, 0x04, 0x0a, 0x1c, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x72, 0x67, 0x69, + 0x6e, 0x52, 0x61, 0x74, 0x65, 0x73, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, + 0x12, 0x14, 0x0a, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, + 0x63, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, + 0x63, 0x79, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x64, 0x61, 0x74, 0x65, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x44, 0x61, 0x74, + 0x65, 0x12, 0x19, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x07, 0x65, 0x6e, 0x64, 0x44, 0x61, 0x74, 0x65, 0x12, 0x2c, 0x0a, 0x12, + 0x67, 0x65, 0x74, 0x5f, 0x70, 0x72, 0x65, 0x64, 0x69, 0x63, 0x74, 0x65, 0x64, 0x5f, 0x72, 0x61, + 0x74, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x67, 0x65, 0x74, 0x50, 0x72, 0x65, + 0x64, 0x69, 0x63, 0x74, 0x65, 0x64, 0x52, 0x61, 0x74, 0x65, 0x12, 0x30, 0x0a, 0x14, 0x67, 0x65, + 0x74, 0x5f, 0x6c, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, + 0x74, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x67, 0x65, 0x74, 0x4c, 0x65, 0x6e, + 0x64, 0x69, 0x6e, 0x67, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x28, 0x0a, 0x10, + 0x67, 0x65, 0x74, 0x5f, 0x62, 0x6f, 0x72, 0x72, 0x6f, 0x77, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x73, + 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x67, 0x65, 0x74, 0x42, 0x6f, 0x72, 0x72, 0x6f, + 0x77, 0x52, 0x61, 0x74, 0x65, 0x73, 0x12, 0x28, 0x0a, 0x10, 0x67, 0x65, 0x74, 0x5f, 0x62, 0x6f, + 0x72, 0x72, 0x6f, 0x77, 0x5f, 0x63, 0x6f, 0x73, 0x74, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x0e, 0x67, 0x65, 0x74, 0x42, 0x6f, 0x72, 0x72, 0x6f, 0x77, 0x43, 0x6f, 0x73, 0x74, 0x73, + 0x12, 0x2a, 0x0a, 0x11, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x61, 0x6c, 0x6c, 0x5f, + 0x72, 0x61, 0x74, 0x65, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x69, 0x6e, 0x63, + 0x6c, 0x75, 0x64, 0x65, 0x41, 0x6c, 0x6c, 0x52, 0x61, 0x74, 0x65, 0x73, 0x12, 0x2b, 0x0a, 0x11, + 0x63, 0x61, 0x6c, 0x63, 0x75, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x6f, 0x66, 0x66, 0x6c, 0x69, 0x6e, + 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x63, 0x61, 0x6c, 0x63, 0x75, 0x6c, 0x61, + 0x74, 0x65, 0x4f, 0x66, 0x66, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x6b, + 0x65, 0x72, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, 0x0c, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0c, 0x74, 0x61, 0x6b, 0x65, 0x72, 0x46, 0x65, 0x65, 0x52, 0x61, 0x74, 0x65, 0x12, + 0x28, 0x0a, 0x05, 0x72, 0x61, 0x74, 0x65, 0x73, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, + 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x52, 0x61, + 0x74, 0x65, 0x52, 0x05, 0x72, 0x61, 0x74, 0x65, 0x73, 0x22, 0x3e, 0x0a, 0x0e, 0x4c, 0x65, 0x6e, + 0x64, 0x69, 0x6e, 0x67, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x70, + 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x61, + 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x22, 0x34, 0x0a, 0x0a, 0x42, 0x6f, 0x72, + 0x72, 0x6f, 0x77, 0x43, 0x6f, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x73, 0x74, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x63, 0x6f, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x73, + 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x22, + 0xe2, 0x02, 0x0a, 0x0a, 0x4d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x52, 0x61, 0x74, 0x65, 0x12, 0x12, + 0x0a, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x69, + 0x6d, 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x6d, 0x61, 0x72, 0x6b, 0x65, 0x74, 0x5f, 0x62, 0x6f, 0x72, + 0x72, 0x6f, 0x77, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, + 0x6d, 0x61, 0x72, 0x6b, 0x65, 0x74, 0x42, 0x6f, 0x72, 0x72, 0x6f, 0x77, 0x53, 0x69, 0x7a, 0x65, + 0x12, 0x1f, 0x0a, 0x0b, 0x68, 0x6f, 0x75, 0x72, 0x6c, 0x79, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x68, 0x6f, 0x75, 0x72, 0x6c, 0x79, 0x52, 0x61, 0x74, + 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x79, 0x65, 0x61, 0x72, 0x6c, 0x79, 0x5f, 0x72, 0x61, 0x74, 0x65, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x79, 0x65, 0x61, 0x72, 0x6c, 0x79, 0x52, 0x61, + 0x74, 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x68, 0x6f, 0x75, 0x72, 0x6c, 0x79, 0x5f, 0x62, 0x6f, 0x72, + 0x72, 0x6f, 0x77, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, + 0x68, 0x6f, 0x75, 0x72, 0x6c, 0x79, 0x42, 0x6f, 0x72, 0x72, 0x6f, 0x77, 0x52, 0x61, 0x74, 0x65, + 0x12, 0x2c, 0x0a, 0x12, 0x79, 0x65, 0x61, 0x72, 0x6c, 0x79, 0x5f, 0x62, 0x6f, 0x72, 0x72, 0x6f, + 0x77, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x79, 0x65, + 0x61, 0x72, 0x6c, 0x79, 0x42, 0x6f, 0x72, 0x72, 0x6f, 0x77, 0x52, 0x61, 0x74, 0x65, 0x12, 0x3f, + 0x0a, 0x0f, 0x6c, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, + 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, + 0x2e, 0x4c, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, + 0x0e, 0x6c, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, + 0x33, 0x0a, 0x0b, 0x62, 0x6f, 0x72, 0x72, 0x6f, 0x77, 0x5f, 0x63, 0x6f, 0x73, 0x74, 0x18, 0x08, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x6f, + 0x72, 0x72, 0x6f, 0x77, 0x43, 0x6f, 0x73, 0x74, 0x52, 0x0a, 0x62, 0x6f, 0x72, 0x72, 0x6f, 0x77, + 0x43, 0x6f, 0x73, 0x74, 0x22, 0xae, 0x03, 0x0a, 0x1d, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x72, 0x67, + 0x69, 0x6e, 0x52, 0x61, 0x74, 0x65, 0x73, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x28, 0x0a, 0x05, 0x72, 0x61, 0x74, 0x65, 0x73, 0x18, + 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4d, + 0x61, 0x72, 0x67, 0x69, 0x6e, 0x52, 0x61, 0x74, 0x65, 0x52, 0x05, 0x72, 0x61, 0x74, 0x65, 0x73, + 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x73, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x52, 0x61, 0x74, 0x65, + 0x73, 0x12, 0x28, 0x0a, 0x10, 0x73, 0x75, 0x6d, 0x5f, 0x62, 0x6f, 0x72, 0x72, 0x6f, 0x77, 0x5f, + 0x63, 0x6f, 0x73, 0x74, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x73, 0x75, 0x6d, + 0x42, 0x6f, 0x72, 0x72, 0x6f, 0x77, 0x43, 0x6f, 0x73, 0x74, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x61, + 0x76, 0x67, 0x5f, 0x62, 0x6f, 0x72, 0x72, 0x6f, 0x77, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x61, 0x76, 0x67, 0x42, 0x6f, 0x72, 0x72, 0x6f, 0x77, 0x53, + 0x69, 0x7a, 0x65, 0x12, 0x30, 0x0a, 0x14, 0x73, 0x75, 0x6d, 0x5f, 0x6c, 0x65, 0x6e, 0x64, 0x69, + 0x6e, 0x67, 0x5f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x12, 0x73, 0x75, 0x6d, 0x4c, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x50, 0x61, 0x79, + 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x28, 0x0a, 0x10, 0x61, 0x76, 0x67, 0x5f, 0x6c, 0x65, 0x6e, + 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0e, 0x61, 0x76, 0x67, 0x4c, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x69, 0x7a, 0x65, 0x12, + 0x33, 0x0a, 0x0b, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, 0x07, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x61, + 0x72, 0x67, 0x69, 0x6e, 0x52, 0x61, 0x74, 0x65, 0x52, 0x0a, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, + 0x52, 0x61, 0x74, 0x65, 0x12, 0x39, 0x0a, 0x0e, 0x70, 0x72, 0x65, 0x64, 0x69, 0x63, 0x74, 0x65, + 0x64, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x67, + 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x52, 0x61, 0x74, 0x65, + 0x52, 0x0d, 0x70, 0x72, 0x65, 0x64, 0x69, 0x63, 0x74, 0x65, 0x64, 0x52, 0x61, 0x74, 0x65, 0x12, + 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x6b, 0x65, 0x72, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x72, 0x61, 0x74, + 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x74, 0x61, 0x6b, 0x65, 0x72, 0x46, 0x65, + 0x65, 0x52, 0x61, 0x74, 0x65, 0x22, 0xf9, 0x01, 0x0a, 0x1b, 0x47, 0x65, 0x74, 0x4f, 0x72, 0x64, + 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x4d, 0x6f, 0x76, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, + 0x65, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x12, 0x28, 0x0a, 0x04, 0x70, 0x61, 0x69, 0x72, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, + 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x50, 0x61, 0x69, 0x72, 0x52, 0x04, 0x70, 0x61, 0x69, + 0x72, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x01, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x65, 0x6c, + 0x6c, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x73, 0x65, 0x6c, 0x6c, 0x12, 0x36, 0x0a, + 0x17, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x73, 0x5f, 0x72, 0x65, 0x73, 0x74, 0x5f, 0x6f, + 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x15, + 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x73, 0x52, 0x65, 0x73, 0x74, 0x4f, 0x72, 0x64, 0x65, + 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x75, 0x72, 0x63, 0x68, 0x61, 0x73, + 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x70, 0x75, 0x72, 0x63, 0x68, 0x61, 0x73, + 0x65, 0x22, 0xc6, 0x04, 0x0a, 0x1c, 0x47, 0x65, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, + 0x6f, 0x6b, 0x4d, 0x6f, 0x76, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x2d, 0x0a, 0x12, 0x6e, 0x6f, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x70, 0x65, + 0x72, 0x63, 0x65, 0x6e, 0x74, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x01, 0x52, 0x11, + 0x6e, 0x6f, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x50, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x61, 0x67, + 0x65, 0x12, 0x2b, 0x0a, 0x11, 0x69, 0x6d, 0x70, 0x61, 0x63, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x63, + 0x65, 0x6e, 0x74, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x10, 0x69, 0x6d, + 0x70, 0x61, 0x63, 0x74, 0x50, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x61, 0x67, 0x65, 0x12, 0x23, + 0x0a, 0x0d, 0x73, 0x6c, 0x69, 0x70, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x73, 0x74, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0c, 0x73, 0x6c, 0x69, 0x70, 0x70, 0x61, 0x67, 0x65, 0x43, + 0x6f, 0x73, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x5f, + 0x62, 0x6f, 0x75, 0x67, 0x68, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x63, 0x75, + 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x42, 0x6f, 0x75, 0x67, 0x68, 0x74, 0x12, 0x16, 0x0a, 0x06, + 0x62, 0x6f, 0x75, 0x67, 0x68, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x01, 0x52, 0x06, 0x62, 0x6f, + 0x75, 0x67, 0x68, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, + 0x5f, 0x73, 0x6f, 0x6c, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x75, 0x72, + 0x72, 0x65, 0x6e, 0x63, 0x79, 0x53, 0x6f, 0x6c, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x6f, 0x6c, + 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x01, 0x52, 0x04, 0x73, 0x6f, 0x6c, 0x64, 0x12, 0x23, 0x0a, + 0x0d, 0x73, 0x69, 0x64, 0x65, 0x5f, 0x61, 0x66, 0x66, 0x65, 0x63, 0x74, 0x65, 0x64, 0x18, 0x08, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x73, 0x69, 0x64, 0x65, 0x41, 0x66, 0x66, 0x65, 0x63, 0x74, + 0x65, 0x64, 0x12, 0x27, 0x0a, 0x0f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x75, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x3f, 0x0a, 0x1c, 0x66, + 0x75, 0x6c, 0x6c, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x5f, 0x73, 0x69, + 0x64, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x19, 0x66, 0x75, 0x6c, 0x6c, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, + 0x53, 0x69, 0x64, 0x65, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x64, 0x12, 0x1f, 0x0a, 0x0b, + 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, + 0x01, 0x52, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, 0x1b, 0x0a, + 0x09, 0x65, 0x6e, 0x64, 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x01, + 0x52, 0x08, 0x65, 0x6e, 0x64, 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, 0x30, 0x0a, 0x14, 0x6e, 0x6f, + 0x5f, 0x73, 0x6c, 0x69, 0x70, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x6f, 0x63, 0x63, 0x75, 0x72, 0x72, + 0x65, 0x64, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x6e, 0x6f, 0x53, 0x6c, 0x69, 0x70, + 0x70, 0x61, 0x67, 0x65, 0x4f, 0x63, 0x63, 0x75, 0x72, 0x72, 0x65, 0x64, 0x12, 0x2c, 0x0a, 0x12, 0x61, 0x76, 0x65, 0x72, 0x61, 0x67, 0x65, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x63, 0x6f, - 0x73, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x01, 0x52, 0x10, 0x61, 0x76, 0x65, 0x72, 0x61, 0x67, - 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x73, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x73, 0x69, - 0x64, 0x65, 0x5f, 0x61, 0x66, 0x66, 0x65, 0x63, 0x74, 0x65, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0c, 0x73, 0x69, 0x64, 0x65, 0x41, 0x66, 0x66, 0x65, 0x63, 0x74, 0x65, 0x64, 0x12, - 0x53, 0x0a, 0x26, 0x61, 0x70, 0x70, 0x72, 0x6f, 0x78, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x5f, 0x69, - 0x6d, 0x70, 0x61, 0x63, 0x74, 0x5f, 0x73, 0x6c, 0x69, 0x70, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x70, - 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x61, 0x67, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x01, 0x52, - 0x23, 0x61, 0x70, 0x70, 0x72, 0x6f, 0x78, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x49, 0x6d, 0x70, 0x61, - 0x63, 0x74, 0x53, 0x6c, 0x69, 0x70, 0x70, 0x61, 0x67, 0x65, 0x50, 0x65, 0x72, 0x63, 0x65, 0x6e, - 0x74, 0x61, 0x67, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x75, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x3f, 0x0a, - 0x1c, 0x66, 0x75, 0x6c, 0x6c, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x5f, - 0x73, 0x69, 0x64, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x64, 0x18, 0x0b, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x19, 0x66, 0x75, 0x6c, 0x6c, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, - 0x6f, 0x6b, 0x53, 0x69, 0x64, 0x65, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x64, 0x22, 0x69, - 0x0a, 0x16, 0x47, 0x65, 0x74, 0x4f, 0x70, 0x65, 0x6e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x65, 0x73, - 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, - 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, - 0x61, 0x6e, 0x67, 0x65, 0x12, 0x33, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x70, 0x65, 0x6e, - 0x49, 0x6e, 0x74, 0x65, 0x72, 0x65, 0x73, 0x74, 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x59, 0x0a, 0x17, 0x4f, 0x70, 0x65, - 0x6e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x65, 0x73, 0x74, 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x12, 0x28, 0x0a, 0x04, 0x70, 0x61, - 0x69, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, - 0x63, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x50, 0x61, 0x69, 0x72, 0x52, 0x04, - 0x70, 0x61, 0x69, 0x72, 0x22, 0x4f, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x4f, 0x70, 0x65, 0x6e, 0x49, - 0x6e, 0x74, 0x65, 0x72, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x34, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, - 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x70, 0x65, 0x6e, 0x49, 0x6e, 0x74, 0x65, 0x72, - 0x65, 0x73, 0x74, 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, - 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x9b, 0x01, 0x0a, 0x18, 0x4f, 0x70, 0x65, 0x6e, 0x49, 0x6e, - 0x74, 0x65, 0x72, 0x65, 0x73, 0x74, 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x14, - 0x0a, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, - 0x73, 0x73, 0x65, 0x74, 0x12, 0x28, 0x0a, 0x04, 0x70, 0x61, 0x69, 0x72, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x75, 0x72, 0x72, - 0x65, 0x6e, 0x63, 0x79, 0x50, 0x61, 0x69, 0x72, 0x52, 0x04, 0x70, 0x61, 0x69, 0x72, 0x12, 0x23, - 0x0a, 0x0d, 0x6f, 0x70, 0x65, 0x6e, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x65, 0x73, 0x74, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0c, 0x6f, 0x70, 0x65, 0x6e, 0x49, 0x6e, 0x74, 0x65, 0x72, - 0x65, 0x73, 0x74, 0x22, 0x78, 0x0a, 0x1a, 0x47, 0x65, 0x74, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, - 0x63, 0x79, 0x54, 0x72, 0x61, 0x64, 0x65, 0x55, 0x52, 0x4c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x73, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x01, 0x52, 0x10, 0x61, 0x76, 0x65, 0x72, 0x61, 0x67, + 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x73, 0x74, 0x22, 0xfb, 0x01, 0x0a, 0x22, 0x47, + 0x65, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x41, 0x6d, 0x6f, 0x75, 0x6e, + 0x74, 0x42, 0x79, 0x4e, 0x6f, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x12, 0x28, 0x0a, 0x04, 0x70, 0x61, 0x69, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, - 0x6e, 0x63, 0x79, 0x50, 0x61, 0x69, 0x72, 0x52, 0x04, 0x70, 0x61, 0x69, 0x72, 0x22, 0x2f, 0x0a, - 0x1b, 0x47, 0x65, 0x74, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x54, 0x72, 0x61, 0x64, - 0x65, 0x55, 0x52, 0x4c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x10, 0x0a, 0x03, - 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x32, 0x9a, - 0x6c, 0x0a, 0x15, 0x47, 0x6f, 0x43, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x54, 0x72, 0x61, 0x64, 0x65, - 0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x4f, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x49, - 0x6e, 0x66, 0x6f, 0x12, 0x16, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, - 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x67, 0x63, - 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x13, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0d, 0x12, 0x0b, 0x2f, 0x76, - 0x31, 0x2f, 0x67, 0x65, 0x74, 0x69, 0x6e, 0x66, 0x6f, 0x12, 0x67, 0x0a, 0x0d, 0x47, 0x65, 0x74, - 0x53, 0x75, 0x62, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x73, 0x12, 0x1c, 0x2e, 0x67, 0x63, 0x74, - 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x75, 0x62, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, - 0x63, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x75, 0x73, 0x62, 0x73, 0x79, 0x74, 0x65, 0x6d, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x19, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x13, 0x12, - 0x11, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x73, 0x75, 0x62, 0x73, 0x79, 0x73, 0x74, 0x65, - 0x6d, 0x73, 0x12, 0x68, 0x0a, 0x0f, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x53, 0x75, 0x62, 0x73, - 0x79, 0x73, 0x74, 0x65, 0x6d, 0x12, 0x1f, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, - 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x53, 0x75, 0x62, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, - 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, - 0x1b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x15, 0x12, 0x13, 0x2f, 0x76, 0x31, 0x2f, 0x65, 0x6e, 0x61, - 0x62, 0x6c, 0x65, 0x73, 0x75, 0x62, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x12, 0x6a, 0x0a, 0x10, - 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x53, 0x75, 0x62, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, - 0x12, 0x1f, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, - 0x63, 0x53, 0x75, 0x62, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x17, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, - 0x69, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1c, 0x82, 0xd3, 0xe4, 0x93, - 0x02, 0x16, 0x12, 0x14, 0x2f, 0x76, 0x31, 0x2f, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x73, - 0x75, 0x62, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x12, 0x6f, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x52, - 0x50, 0x43, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x12, 0x1e, 0x2e, 0x67, 0x63, - 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x50, 0x43, 0x45, 0x6e, 0x64, 0x70, 0x6f, - 0x69, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x67, 0x63, - 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x50, 0x43, 0x45, 0x6e, 0x64, 0x70, 0x6f, - 0x69, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1b, 0x82, 0xd3, - 0xe4, 0x93, 0x02, 0x15, 0x12, 0x13, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x72, 0x70, 0x63, - 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x12, 0x93, 0x01, 0x0a, 0x18, 0x47, 0x65, - 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x75, 0x6e, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, - 0x6c, 0x61, 0x79, 0x65, 0x72, 0x73, 0x12, 0x27, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, + 0x6e, 0x63, 0x79, 0x50, 0x61, 0x69, 0x72, 0x52, 0x04, 0x70, 0x61, 0x69, 0x72, 0x12, 0x2d, 0x0a, + 0x12, 0x6e, 0x6f, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x70, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, + 0x61, 0x67, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x01, 0x52, 0x11, 0x6e, 0x6f, 0x6d, 0x69, 0x6e, + 0x61, 0x6c, 0x50, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x61, 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, + 0x73, 0x65, 0x6c, 0x6c, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x73, 0x65, 0x6c, 0x6c, + 0x12, 0x36, 0x0a, 0x17, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x73, 0x5f, 0x72, 0x65, 0x73, + 0x74, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x18, 0x08, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x15, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x73, 0x52, 0x65, 0x73, 0x74, 0x4f, + 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x22, 0x9d, 0x04, 0x0a, 0x23, 0x47, 0x65, 0x74, + 0x4f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x42, + 0x79, 0x4e, 0x6f, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x27, 0x0a, 0x0f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x69, + 0x72, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0e, 0x61, 0x6d, 0x6f, 0x75, 0x6e, + 0x74, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x12, 0x29, 0x0a, 0x10, 0x63, 0x75, 0x72, + 0x72, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x73, 0x65, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x53, 0x65, 0x6c, + 0x6c, 0x69, 0x6e, 0x67, 0x12, 0x27, 0x0a, 0x0f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x72, + 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0e, 0x61, + 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x64, 0x12, 0x27, 0x0a, + 0x0f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x62, 0x75, 0x79, 0x69, 0x6e, 0x67, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, + 0x42, 0x75, 0x79, 0x69, 0x6e, 0x67, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, + 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0a, 0x73, 0x74, 0x61, + 0x72, 0x74, 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x65, 0x6e, 0x64, 0x5f, 0x70, + 0x72, 0x69, 0x63, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x01, 0x52, 0x08, 0x65, 0x6e, 0x64, 0x50, + 0x72, 0x69, 0x63, 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x61, 0x76, 0x65, 0x72, 0x61, 0x67, 0x65, 0x5f, + 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x73, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x01, + 0x52, 0x10, 0x61, 0x76, 0x65, 0x72, 0x61, 0x67, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x43, 0x6f, + 0x73, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x73, 0x69, 0x64, 0x65, 0x5f, 0x61, 0x66, 0x66, 0x65, 0x63, + 0x74, 0x65, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x73, 0x69, 0x64, 0x65, 0x41, + 0x66, 0x66, 0x65, 0x63, 0x74, 0x65, 0x64, 0x12, 0x55, 0x0a, 0x27, 0x61, 0x70, 0x70, 0x72, 0x6f, + 0x78, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x5f, 0x6e, 0x6f, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x73, + 0x6c, 0x69, 0x70, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x70, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x61, + 0x67, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x01, 0x52, 0x24, 0x61, 0x70, 0x70, 0x72, 0x6f, 0x78, + 0x69, 0x6d, 0x61, 0x74, 0x65, 0x4e, 0x6f, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x53, 0x6c, 0x69, 0x70, + 0x70, 0x61, 0x67, 0x65, 0x50, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x61, 0x67, 0x65, 0x12, 0x27, + 0x0a, 0x0f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, + 0x6c, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, + 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x3f, 0x0a, 0x1c, 0x66, 0x75, 0x6c, 0x6c, 0x5f, + 0x6f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x5f, 0x73, 0x69, 0x64, 0x65, 0x5f, 0x63, + 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x08, 0x52, 0x19, 0x66, + 0x75, 0x6c, 0x6c, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x53, 0x69, 0x64, 0x65, + 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x64, 0x22, 0xf8, 0x01, 0x0a, 0x21, 0x47, 0x65, 0x74, + 0x4f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x42, + 0x79, 0x49, 0x6d, 0x70, 0x61, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, + 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x73, + 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, + 0x12, 0x28, 0x0a, 0x04, 0x70, 0x61, 0x69, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, + 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, + 0x50, 0x61, 0x69, 0x72, 0x52, 0x04, 0x70, 0x61, 0x69, 0x72, 0x12, 0x2b, 0x0a, 0x11, 0x69, 0x6d, + 0x70, 0x61, 0x63, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x61, 0x67, 0x65, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x01, 0x52, 0x10, 0x69, 0x6d, 0x70, 0x61, 0x63, 0x74, 0x50, 0x65, 0x72, + 0x63, 0x65, 0x6e, 0x74, 0x61, 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x65, 0x6c, 0x6c, 0x18, + 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x73, 0x65, 0x6c, 0x6c, 0x12, 0x36, 0x0a, 0x17, 0x72, + 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x73, 0x5f, 0x72, 0x65, 0x73, 0x74, 0x5f, 0x6f, 0x72, 0x64, + 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x15, 0x72, 0x65, + 0x71, 0x75, 0x69, 0x72, 0x65, 0x73, 0x52, 0x65, 0x73, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x62, + 0x6f, 0x6f, 0x6b, 0x22, 0x9a, 0x04, 0x0a, 0x22, 0x47, 0x65, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, + 0x62, 0x6f, 0x6f, 0x6b, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x79, 0x49, 0x6d, 0x70, 0x61, + 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x61, 0x6d, + 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x01, 0x52, 0x0e, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x69, + 0x72, 0x65, 0x64, 0x12, 0x29, 0x0a, 0x10, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x5f, + 0x73, 0x65, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x63, + 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x53, 0x65, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x12, 0x27, + 0x0a, 0x0f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, + 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0e, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x52, + 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x64, 0x12, 0x27, 0x0a, 0x0f, 0x63, 0x75, 0x72, 0x72, 0x65, + 0x6e, 0x63, 0x79, 0x5f, 0x62, 0x75, 0x79, 0x69, 0x6e, 0x67, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x42, 0x75, 0x79, 0x69, 0x6e, 0x67, + 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x50, 0x72, 0x69, 0x63, + 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x65, 0x6e, 0x64, 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x01, 0x52, 0x08, 0x65, 0x6e, 0x64, 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, 0x2c, + 0x0a, 0x12, 0x61, 0x76, 0x65, 0x72, 0x61, 0x67, 0x65, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, + 0x63, 0x6f, 0x73, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x01, 0x52, 0x10, 0x61, 0x76, 0x65, 0x72, + 0x61, 0x67, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x73, 0x74, 0x12, 0x23, 0x0a, 0x0d, + 0x73, 0x69, 0x64, 0x65, 0x5f, 0x61, 0x66, 0x66, 0x65, 0x63, 0x74, 0x65, 0x64, 0x18, 0x08, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0c, 0x73, 0x69, 0x64, 0x65, 0x41, 0x66, 0x66, 0x65, 0x63, 0x74, 0x65, + 0x64, 0x12, 0x53, 0x0a, 0x26, 0x61, 0x70, 0x70, 0x72, 0x6f, 0x78, 0x69, 0x6d, 0x61, 0x74, 0x65, + 0x5f, 0x69, 0x6d, 0x70, 0x61, 0x63, 0x74, 0x5f, 0x73, 0x6c, 0x69, 0x70, 0x70, 0x61, 0x67, 0x65, + 0x5f, 0x70, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x61, 0x67, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, + 0x01, 0x52, 0x23, 0x61, 0x70, 0x70, 0x72, 0x6f, 0x78, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x49, 0x6d, + 0x70, 0x61, 0x63, 0x74, 0x53, 0x6c, 0x69, 0x70, 0x70, 0x61, 0x67, 0x65, 0x50, 0x65, 0x72, 0x63, + 0x65, 0x6e, 0x74, 0x61, 0x67, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0e, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, + 0x3f, 0x0a, 0x1c, 0x66, 0x75, 0x6c, 0x6c, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, + 0x6b, 0x5f, 0x73, 0x69, 0x64, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x64, 0x18, + 0x0b, 0x20, 0x01, 0x28, 0x08, 0x52, 0x19, 0x66, 0x75, 0x6c, 0x6c, 0x4f, 0x72, 0x64, 0x65, 0x72, + 0x62, 0x6f, 0x6f, 0x6b, 0x53, 0x69, 0x64, 0x65, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x64, + 0x22, 0x69, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x4f, 0x70, 0x65, 0x6e, 0x49, 0x6e, 0x74, 0x65, 0x72, + 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, + 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, + 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x33, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x70, + 0x65, 0x6e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x65, 0x73, 0x74, 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x59, 0x0a, 0x17, 0x4f, + 0x70, 0x65, 0x6e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x65, 0x73, 0x74, 0x44, 0x61, 0x74, 0x61, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x12, 0x28, 0x0a, 0x04, + 0x70, 0x61, 0x69, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x63, 0x74, + 0x72, 0x70, 0x63, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x50, 0x61, 0x69, 0x72, + 0x52, 0x04, 0x70, 0x61, 0x69, 0x72, 0x22, 0x4f, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x4f, 0x70, 0x65, + 0x6e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x34, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x20, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x70, 0x65, 0x6e, 0x49, 0x6e, 0x74, + 0x65, 0x72, 0x65, 0x73, 0x74, 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x9b, 0x01, 0x0a, 0x18, 0x4f, 0x70, 0x65, 0x6e, + 0x49, 0x6e, 0x74, 0x65, 0x72, 0x65, 0x73, 0x74, 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, + 0x12, 0x14, 0x0a, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x12, 0x28, 0x0a, 0x04, 0x70, 0x61, 0x69, 0x72, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x75, + 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x50, 0x61, 0x69, 0x72, 0x52, 0x04, 0x70, 0x61, 0x69, 0x72, + 0x12, 0x23, 0x0a, 0x0d, 0x6f, 0x70, 0x65, 0x6e, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x65, 0x73, + 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0c, 0x6f, 0x70, 0x65, 0x6e, 0x49, 0x6e, 0x74, + 0x65, 0x72, 0x65, 0x73, 0x74, 0x22, 0x78, 0x0a, 0x1a, 0x47, 0x65, 0x74, 0x43, 0x75, 0x72, 0x72, + 0x65, 0x6e, 0x63, 0x79, 0x54, 0x72, 0x61, 0x64, 0x65, 0x55, 0x52, 0x4c, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, + 0x14, 0x0a, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, + 0x61, 0x73, 0x73, 0x65, 0x74, 0x12, 0x28, 0x0a, 0x04, 0x70, 0x61, 0x69, 0x72, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x75, 0x72, + 0x72, 0x65, 0x6e, 0x63, 0x79, 0x50, 0x61, 0x69, 0x72, 0x52, 0x04, 0x70, 0x61, 0x69, 0x72, 0x22, + 0x2f, 0x0a, 0x1b, 0x47, 0x65, 0x74, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x54, 0x72, + 0x61, 0x64, 0x65, 0x55, 0x52, 0x4c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x10, + 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, + 0x32, 0x9a, 0x6c, 0x0a, 0x15, 0x47, 0x6f, 0x43, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x54, 0x72, 0x61, + 0x64, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x4f, 0x0a, 0x07, 0x47, 0x65, + 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x16, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, + 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, + 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x13, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0d, 0x12, 0x0b, + 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x69, 0x6e, 0x66, 0x6f, 0x12, 0x67, 0x0a, 0x0d, 0x47, + 0x65, 0x74, 0x53, 0x75, 0x62, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x73, 0x12, 0x1c, 0x2e, 0x67, + 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x75, 0x62, 0x73, 0x79, 0x73, 0x74, + 0x65, 0x6d, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x67, 0x63, 0x74, + 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x75, 0x73, 0x62, 0x73, 0x79, 0x74, 0x65, 0x6d, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x19, 0x82, 0xd3, 0xe4, 0x93, 0x02, + 0x13, 0x12, 0x11, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x73, 0x75, 0x62, 0x73, 0x79, 0x73, + 0x74, 0x65, 0x6d, 0x73, 0x12, 0x68, 0x0a, 0x0f, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x53, 0x75, + 0x62, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x12, 0x1f, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, + 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x53, 0x75, 0x62, 0x73, 0x79, 0x73, 0x74, 0x65, + 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, + 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x1b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x15, 0x12, 0x13, 0x2f, 0x76, 0x31, 0x2f, 0x65, + 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x75, 0x62, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x12, 0x6a, + 0x0a, 0x10, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x53, 0x75, 0x62, 0x73, 0x79, 0x73, 0x74, + 0x65, 0x6d, 0x12, 0x1f, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x65, + 0x72, 0x69, 0x63, 0x53, 0x75, 0x62, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x6e, + 0x65, 0x72, 0x69, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1c, 0x82, 0xd3, + 0xe4, 0x93, 0x02, 0x16, 0x12, 0x14, 0x2f, 0x76, 0x31, 0x2f, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, + 0x65, 0x73, 0x75, 0x62, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x12, 0x6f, 0x0a, 0x0f, 0x47, 0x65, + 0x74, 0x52, 0x50, 0x43, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x12, 0x1e, 0x2e, + 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x50, 0x43, 0x45, 0x6e, 0x64, + 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, + 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x50, 0x43, 0x45, 0x6e, 0x64, + 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1b, + 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x15, 0x12, 0x13, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x72, + 0x70, 0x63, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x12, 0x93, 0x01, 0x0a, 0x18, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x75, 0x6e, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x52, 0x65, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x28, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6d, 0x6d, - 0x75, 0x6e, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x6c, 0x61, 0x79, 0x65, 0x72, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x24, 0x82, 0xd3, 0xe4, 0x93, 0x02, - 0x1e, 0x12, 0x1c, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x63, 0x6f, 0x6d, 0x6d, 0x75, 0x6e, - 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x73, 0x12, - 0x63, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x12, - 0x1b, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x78, 0x63, 0x68, - 0x61, 0x6e, 0x67, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x67, - 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, - 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x18, 0x82, 0xd3, 0xe4, 0x93, - 0x02, 0x12, 0x12, 0x10, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x65, 0x78, 0x63, 0x68, 0x61, - 0x6e, 0x67, 0x65, 0x73, 0x12, 0x6e, 0x0a, 0x0f, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x45, - 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x22, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, - 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, - 0x4e, 0x61, 0x6d, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x67, 0x63, - 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x18, 0x3a, 0x01, 0x2a, 0x22, - 0x13, 0x2f, 0x76, 0x31, 0x2f, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x65, 0x78, 0x63, 0x68, - 0x61, 0x6e, 0x67, 0x65, 0x12, 0x73, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x45, 0x78, 0x63, 0x68, 0x61, - 0x6e, 0x67, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x22, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, - 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, - 0x4e, 0x61, 0x6d, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x67, 0x63, - 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, - 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1b, 0x82, 0xd3, - 0xe4, 0x93, 0x02, 0x15, 0x12, 0x13, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x65, 0x78, 0x63, - 0x68, 0x61, 0x6e, 0x67, 0x65, 0x69, 0x6e, 0x66, 0x6f, 0x12, 0x74, 0x0a, 0x12, 0x47, 0x65, 0x74, - 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4f, 0x54, 0x50, 0x43, 0x6f, 0x64, 0x65, 0x12, - 0x22, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, - 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, - 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4f, 0x54, 0x50, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0x1a, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x14, 0x12, 0x12, 0x2f, 0x76, 0x31, - 0x2f, 0x67, 0x65, 0x74, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x6f, 0x74, 0x70, 0x12, - 0x73, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4f, 0x54, - 0x50, 0x43, 0x6f, 0x64, 0x65, 0x73, 0x12, 0x1e, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, - 0x47, 0x65, 0x74, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4f, 0x54, 0x50, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, - 0x47, 0x65, 0x74, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4f, 0x54, 0x50, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x15, 0x12, - 0x13, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, - 0x6f, 0x74, 0x70, 0x73, 0x12, 0x6c, 0x0a, 0x0e, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x45, 0x78, - 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x22, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, - 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4e, - 0x61, 0x6d, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x67, 0x63, 0x74, - 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0x1d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x3a, 0x01, 0x2a, 0x22, 0x12, - 0x2f, 0x76, 0x31, 0x2f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, - 0x67, 0x65, 0x12, 0x57, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x72, 0x12, - 0x18, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x69, 0x63, 0x6b, - 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x63, 0x74, 0x72, - 0x70, 0x63, 0x2e, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x22, 0x18, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x12, 0x3a, 0x01, 0x2a, 0x22, 0x0d, 0x2f, 0x76, - 0x31, 0x2f, 0x67, 0x65, 0x74, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x72, 0x12, 0x5b, 0x0a, 0x0a, 0x47, - 0x65, 0x74, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x72, 0x73, 0x12, 0x19, 0x2e, 0x67, 0x63, 0x74, 0x72, - 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, - 0x74, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x16, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x10, 0x12, 0x0e, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, - 0x74, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x72, 0x73, 0x12, 0x63, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x4f, - 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x12, 0x1b, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, - 0x63, 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4f, - 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x1b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x15, 0x3a, 0x01, 0x2a, 0x22, 0x10, 0x2f, 0x76, 0x31, - 0x2f, 0x67, 0x65, 0x74, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x12, 0x67, 0x0a, - 0x0d, 0x47, 0x65, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x73, 0x12, 0x1c, + 0x52, 0x65, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x73, 0x12, 0x27, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, + 0x63, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x75, 0x6e, 0x69, 0x63, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x52, 0x65, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x28, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, + 0x6d, 0x6d, 0x75, 0x6e, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x6c, 0x61, 0x79, + 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x24, 0x82, 0xd3, 0xe4, + 0x93, 0x02, 0x1e, 0x12, 0x1c, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x63, 0x6f, 0x6d, 0x6d, + 0x75, 0x6e, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x65, 0x72, + 0x73, 0x12, 0x63, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, + 0x73, 0x12, 0x1b, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x78, + 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, + 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x78, 0x63, 0x68, 0x61, + 0x6e, 0x67, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x18, 0x82, 0xd3, + 0xe4, 0x93, 0x02, 0x12, 0x12, 0x10, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x65, 0x78, 0x63, + 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x12, 0x6e, 0x0a, 0x0f, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, + 0x65, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x22, 0x2e, 0x67, 0x63, 0x74, 0x72, + 0x70, 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, + 0x67, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, + 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x18, 0x3a, 0x01, + 0x2a, 0x22, 0x13, 0x2f, 0x76, 0x31, 0x2f, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x65, 0x78, + 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x73, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x45, 0x78, 0x63, + 0x68, 0x61, 0x6e, 0x67, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x22, 0x2e, 0x67, 0x63, 0x74, 0x72, + 0x70, 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, + 0x67, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, + 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, + 0x67, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1b, + 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x15, 0x12, 0x13, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x65, + 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x69, 0x6e, 0x66, 0x6f, 0x12, 0x74, 0x0a, 0x12, 0x47, + 0x65, 0x74, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4f, 0x54, 0x50, 0x43, 0x6f, 0x64, + 0x65, 0x12, 0x22, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, + 0x69, 0x63, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, + 0x65, 0x74, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4f, 0x54, 0x50, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1a, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x14, 0x12, 0x12, 0x2f, + 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x6f, 0x74, + 0x70, 0x12, 0x73, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, + 0x4f, 0x54, 0x50, 0x43, 0x6f, 0x64, 0x65, 0x73, 0x12, 0x1e, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, + 0x63, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4f, 0x54, 0x50, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, + 0x63, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4f, 0x54, 0x50, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1b, 0x82, 0xd3, 0xe4, 0x93, 0x02, + 0x15, 0x12, 0x13, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, + 0x67, 0x65, 0x6f, 0x74, 0x70, 0x73, 0x12, 0x6c, 0x0a, 0x0e, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, + 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x22, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, + 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, + 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x67, + 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x3a, 0x01, 0x2a, + 0x22, 0x12, 0x2f, 0x76, 0x31, 0x2f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x65, 0x78, 0x63, 0x68, + 0x61, 0x6e, 0x67, 0x65, 0x12, 0x57, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x54, 0x69, 0x63, 0x6b, 0x65, + 0x72, 0x12, 0x18, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x69, + 0x63, 0x6b, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x63, + 0x74, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x18, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x12, 0x3a, 0x01, 0x2a, 0x22, 0x0d, + 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x72, 0x12, 0x5b, 0x0a, + 0x0a, 0x47, 0x65, 0x74, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x72, 0x73, 0x12, 0x19, 0x2e, 0x67, 0x63, + 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x72, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, + 0x47, 0x65, 0x74, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x16, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x10, 0x12, 0x0e, 0x2f, 0x76, 0x31, 0x2f, + 0x67, 0x65, 0x74, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x72, 0x73, 0x12, 0x63, 0x0a, 0x0c, 0x47, 0x65, + 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x12, 0x1b, 0x2e, 0x67, 0x63, 0x74, + 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, + 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x1b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x15, 0x3a, 0x01, 0x2a, 0x22, 0x10, 0x2f, + 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x12, + 0x67, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x73, + 0x12, 0x1c, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x72, 0x64, + 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, - 0x62, 0x6f, 0x6f, 0x6b, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x67, - 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, - 0x6f, 0x6b, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x19, 0x82, 0xd3, 0xe4, - 0x93, 0x02, 0x13, 0x12, 0x11, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x6f, 0x72, 0x64, 0x65, - 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x73, 0x12, 0x6b, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, - 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1d, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, - 0x63, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, - 0x2e, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1a, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x14, 0x12, - 0x12, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x69, - 0x6e, 0x66, 0x6f, 0x12, 0x71, 0x0a, 0x11, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x63, 0x63, - 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1d, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, - 0x63, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, - 0x2e, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x12, - 0x15, 0x2f, 0x76, 0x31, 0x2f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x61, 0x63, 0x63, 0x6f, 0x75, - 0x6e, 0x74, 0x69, 0x6e, 0x66, 0x6f, 0x12, 0x79, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, - 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x1d, - 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, - 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, - 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, - 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x20, 0x82, - 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x12, 0x18, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x61, 0x63, - 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x69, 0x6e, 0x66, 0x6f, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x30, - 0x01, 0x12, 0x57, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x18, - 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, - 0x63, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0x15, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0f, 0x12, 0x0d, 0x2f, 0x76, 0x31, - 0x2f, 0x67, 0x65, 0x74, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x63, 0x0a, 0x0c, 0x47, 0x65, - 0x74, 0x50, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x12, 0x1b, 0x2e, 0x67, 0x63, 0x74, - 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, - 0x2e, 0x47, 0x65, 0x74, 0x50, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x18, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x12, 0x12, 0x10, 0x2f, - 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x12, - 0x7f, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x50, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x53, - 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x22, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, - 0x47, 0x65, 0x74, 0x50, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x53, 0x75, 0x6d, 0x6d, - 0x61, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x67, 0x63, 0x74, - 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, - 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, - 0x1f, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x12, 0x17, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, - 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, - 0x12, 0x76, 0x0a, 0x13, 0x41, 0x64, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, - 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x22, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, - 0x2e, 0x41, 0x64, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x41, 0x64, 0x64, + 0x62, 0x6f, 0x6f, 0x6b, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x19, 0x82, + 0xd3, 0xe4, 0x93, 0x02, 0x13, 0x12, 0x11, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x6f, 0x72, + 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x73, 0x12, 0x6b, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x41, + 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1d, 0x2e, 0x67, 0x63, 0x74, + 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x6e, + 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x67, 0x63, 0x74, 0x72, + 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x6e, 0x66, + 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1a, 0x82, 0xd3, 0xe4, 0x93, 0x02, + 0x14, 0x12, 0x12, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x69, 0x6e, 0x66, 0x6f, 0x12, 0x71, 0x0a, 0x11, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, + 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1d, 0x2e, 0x67, 0x63, 0x74, + 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x6e, + 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x67, 0x63, 0x74, 0x72, + 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x6e, 0x66, + 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1d, 0x82, 0xd3, 0xe4, 0x93, 0x02, + 0x17, 0x12, 0x15, 0x2f, 0x76, 0x31, 0x2f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x61, 0x63, 0x63, + 0x6f, 0x75, 0x6e, 0x74, 0x69, 0x6e, 0x66, 0x6f, 0x12, 0x79, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x41, + 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, + 0x12, 0x1d, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, + 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x1e, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x20, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x12, 0x18, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, + 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x69, 0x6e, 0x66, 0x6f, 0x73, 0x74, 0x72, 0x65, 0x61, + 0x6d, 0x30, 0x01, 0x12, 0x57, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x12, 0x18, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x67, 0x63, 0x74, + 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x15, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0f, 0x12, 0x0d, 0x2f, + 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x63, 0x0a, 0x0c, + 0x47, 0x65, 0x74, 0x50, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x12, 0x1b, 0x2e, 0x67, + 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, + 0x69, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x67, 0x63, 0x74, 0x72, + 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x18, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x12, 0x12, + 0x10, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, + 0x6f, 0x12, 0x7f, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x50, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, + 0x6f, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x22, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, + 0x63, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x53, 0x75, + 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x67, + 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, + 0x69, 0x6f, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x1f, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x12, 0x17, 0x2f, 0x76, 0x31, 0x2f, 0x67, + 0x65, 0x74, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x73, 0x75, 0x6d, 0x6d, 0x61, + 0x72, 0x79, 0x12, 0x76, 0x0a, 0x13, 0x41, 0x64, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, + 0x69, 0x6f, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x22, 0x2e, 0x67, 0x63, 0x74, 0x72, + 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x41, + 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, + 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x22, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1c, 0x3a, 0x01, + 0x2a, 0x22, 0x17, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x64, 0x64, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, + 0x6c, 0x69, 0x6f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x7f, 0x0a, 0x16, 0x52, 0x65, + 0x6d, 0x6f, 0x76, 0x65, 0x50, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x41, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x12, 0x25, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, + 0x6d, 0x6f, 0x76, 0x65, 0x50, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x22, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1c, 0x3a, 0x01, 0x2a, 0x22, - 0x17, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x64, 0x64, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, - 0x6f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x7f, 0x0a, 0x16, 0x52, 0x65, 0x6d, 0x6f, - 0x76, 0x65, 0x50, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x41, 0x64, 0x64, 0x72, 0x65, - 0x73, 0x73, 0x12, 0x25, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x6d, 0x6f, - 0x76, 0x65, 0x50, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x41, 0x64, 0x64, 0x72, 0x65, - 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x67, 0x63, 0x74, 0x72, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x25, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1f, 0x3a, 0x01, 0x2a, 0x22, + 0x1a, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x66, + 0x6f, 0x6c, 0x69, 0x6f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x77, 0x0a, 0x11, 0x47, + 0x65, 0x74, 0x46, 0x6f, 0x72, 0x65, 0x78, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, + 0x12, 0x20, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x46, 0x6f, 0x72, + 0x65, 0x78, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x46, + 0x6f, 0x72, 0x65, 0x78, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x12, 0x15, 0x2f, + 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x66, 0x6f, 0x72, 0x65, 0x78, 0x70, 0x72, 0x6f, 0x76, 0x69, + 0x64, 0x65, 0x72, 0x73, 0x12, 0x67, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x46, 0x6f, 0x72, 0x65, 0x78, + 0x52, 0x61, 0x74, 0x65, 0x73, 0x12, 0x1c, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, + 0x65, 0x74, 0x46, 0x6f, 0x72, 0x65, 0x78, 0x52, 0x61, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, + 0x46, 0x6f, 0x72, 0x65, 0x78, 0x52, 0x61, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x19, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x13, 0x12, 0x11, 0x2f, 0x76, 0x31, 0x2f, + 0x67, 0x65, 0x74, 0x66, 0x6f, 0x72, 0x65, 0x78, 0x72, 0x61, 0x74, 0x65, 0x73, 0x12, 0x5a, 0x0a, + 0x09, 0x47, 0x65, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x12, 0x18, 0x2e, 0x67, 0x63, 0x74, + 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, + 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x18, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x12, 0x3a, 0x01, 0x2a, 0x22, 0x0d, 0x2f, 0x76, 0x31, 0x2f, + 0x67, 0x65, 0x74, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x12, 0x52, 0x0a, 0x08, 0x47, 0x65, 0x74, + 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x17, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, + 0x65, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, + 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x44, 0x65, 0x74, + 0x61, 0x69, 0x6c, 0x73, 0x22, 0x17, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x11, 0x3a, 0x01, 0x2a, 0x22, + 0x0c, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x62, 0x0a, + 0x0b, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x1a, 0x2e, 0x67, + 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x4f, 0x72, 0x64, 0x65, + 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, + 0x63, 0x2e, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1a, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x14, 0x3a, 0x01, 0x2a, + 0x22, 0x0f, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x6f, 0x72, 0x64, 0x65, + 0x72, 0x12, 0x6a, 0x0a, 0x0d, 0x53, 0x69, 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x65, 0x4f, 0x72, 0x64, + 0x65, 0x72, 0x12, 0x1c, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, 0x6d, 0x75, + 0x6c, 0x61, 0x74, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x1d, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, 0x6d, 0x75, 0x6c, 0x61, + 0x74, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x1c, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x16, 0x3a, 0x01, 0x2a, 0x22, 0x11, 0x2f, 0x76, 0x31, 0x2f, + 0x73, 0x69, 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x65, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x5e, 0x0a, + 0x09, 0x57, 0x68, 0x61, 0x6c, 0x65, 0x42, 0x6f, 0x6d, 0x62, 0x12, 0x18, 0x2e, 0x67, 0x63, 0x74, + 0x72, 0x70, 0x63, 0x2e, 0x57, 0x68, 0x61, 0x6c, 0x65, 0x42, 0x6f, 0x6d, 0x62, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, + 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x18, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x12, 0x3a, 0x01, 0x2a, 0x22, 0x0d, + 0x2f, 0x76, 0x31, 0x2f, 0x77, 0x68, 0x61, 0x6c, 0x65, 0x62, 0x6f, 0x6d, 0x62, 0x12, 0x5e, 0x0a, + 0x0b, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x1a, 0x2e, 0x67, + 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x4f, 0x72, 0x64, 0x65, + 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, + 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x1a, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x14, 0x3a, 0x01, 0x2a, 0x22, 0x0f, 0x2f, 0x76, + 0x31, 0x2f, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x7a, 0x0a, + 0x11, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x42, 0x61, 0x74, 0x63, 0x68, 0x4f, 0x72, 0x64, 0x65, + 0x72, 0x73, 0x12, 0x20, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x61, 0x6e, 0x63, + 0x65, 0x6c, 0x42, 0x61, 0x74, 0x63, 0x68, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x61, + 0x6e, 0x63, 0x65, 0x6c, 0x42, 0x61, 0x74, 0x63, 0x68, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x20, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x3a, + 0x01, 0x2a, 0x22, 0x15, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x62, 0x61, + 0x74, 0x63, 0x68, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x12, 0x72, 0x0a, 0x0f, 0x43, 0x61, 0x6e, + 0x63, 0x65, 0x6c, 0x41, 0x6c, 0x6c, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x12, 0x1e, 0x2e, 0x67, + 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x41, 0x6c, 0x6c, 0x4f, + 0x72, 0x64, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x67, + 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x41, 0x6c, 0x6c, 0x4f, + 0x72, 0x64, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1e, 0x82, + 0xd3, 0xe4, 0x93, 0x02, 0x18, 0x3a, 0x01, 0x2a, 0x22, 0x13, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x61, + 0x6e, 0x63, 0x65, 0x6c, 0x61, 0x6c, 0x6c, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x12, 0x57, 0x0a, + 0x09, 0x47, 0x65, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x18, 0x2e, 0x67, 0x63, 0x74, + 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, + 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x15, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0f, 0x12, 0x0d, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, + 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x56, 0x0a, 0x08, 0x41, 0x64, 0x64, 0x45, 0x76, 0x65, + 0x6e, 0x74, 0x12, 0x17, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x45, + 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x67, 0x63, + 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x17, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x11, 0x3a, 0x01, 0x2a, + 0x22, 0x0c, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x64, 0x64, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x5e, + 0x0a, 0x0b, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x1a, 0x2e, + 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x45, 0x76, 0x65, + 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x25, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1f, 0x3a, 0x01, 0x2a, 0x22, 0x1a, 0x2f, - 0x76, 0x31, 0x2f, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, - 0x69, 0x6f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x77, 0x0a, 0x11, 0x47, 0x65, 0x74, - 0x46, 0x6f, 0x72, 0x65, 0x78, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x12, 0x20, - 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x46, 0x6f, 0x72, 0x65, 0x78, - 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x21, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x46, 0x6f, 0x72, - 0x65, 0x78, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0x1d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x12, 0x15, 0x2f, 0x76, 0x31, - 0x2f, 0x67, 0x65, 0x74, 0x66, 0x6f, 0x72, 0x65, 0x78, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, - 0x72, 0x73, 0x12, 0x67, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x46, 0x6f, 0x72, 0x65, 0x78, 0x52, 0x61, - 0x74, 0x65, 0x73, 0x12, 0x1c, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, - 0x46, 0x6f, 0x72, 0x65, 0x78, 0x52, 0x61, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x1d, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x46, 0x6f, - 0x72, 0x65, 0x78, 0x52, 0x61, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x19, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x13, 0x12, 0x11, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, - 0x74, 0x66, 0x6f, 0x72, 0x65, 0x78, 0x72, 0x61, 0x74, 0x65, 0x73, 0x12, 0x5a, 0x0a, 0x09, 0x47, - 0x65, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x12, 0x18, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, - 0x63, 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x4f, - 0x72, 0x64, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x18, 0x82, - 0xd3, 0xe4, 0x93, 0x02, 0x12, 0x3a, 0x01, 0x2a, 0x22, 0x0d, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, - 0x74, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x12, 0x52, 0x0a, 0x08, 0x47, 0x65, 0x74, 0x4f, 0x72, - 0x64, 0x65, 0x72, 0x12, 0x17, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, - 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x67, - 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x44, 0x65, 0x74, 0x61, 0x69, - 0x6c, 0x73, 0x22, 0x17, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x11, 0x3a, 0x01, 0x2a, 0x22, 0x0c, 0x2f, - 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x62, 0x0a, 0x0b, 0x53, - 0x75, 0x62, 0x6d, 0x69, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x1a, 0x2e, 0x67, 0x63, 0x74, - 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, - 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0x1a, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x14, 0x3a, 0x01, 0x2a, 0x22, 0x0f, - 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x12, - 0x6a, 0x0a, 0x0d, 0x53, 0x69, 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, - 0x12, 0x1c, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, 0x6d, 0x75, 0x6c, 0x61, - 0x74, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, - 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x65, - 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1c, 0x82, - 0xd3, 0xe4, 0x93, 0x02, 0x16, 0x3a, 0x01, 0x2a, 0x22, 0x11, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x69, - 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x65, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x5e, 0x0a, 0x09, 0x57, - 0x68, 0x61, 0x6c, 0x65, 0x42, 0x6f, 0x6d, 0x62, 0x12, 0x18, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, - 0x63, 0x2e, 0x57, 0x68, 0x61, 0x6c, 0x65, 0x42, 0x6f, 0x6d, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, 0x6d, 0x75, - 0x6c, 0x61, 0x74, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x22, 0x18, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x12, 0x3a, 0x01, 0x2a, 0x22, 0x0d, 0x2f, 0x76, - 0x31, 0x2f, 0x77, 0x68, 0x61, 0x6c, 0x65, 0x62, 0x6f, 0x6d, 0x62, 0x12, 0x5e, 0x0a, 0x0b, 0x43, - 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x1a, 0x2e, 0x67, 0x63, 0x74, - 0x72, 0x70, 0x63, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, - 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, - 0x1a, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x14, 0x3a, 0x01, 0x2a, 0x22, 0x0f, 0x2f, 0x76, 0x31, 0x2f, - 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x7a, 0x0a, 0x11, 0x43, - 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x42, 0x61, 0x74, 0x63, 0x68, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, - 0x12, 0x20, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, - 0x42, 0x61, 0x74, 0x63, 0x68, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x61, 0x6e, 0x63, - 0x65, 0x6c, 0x42, 0x61, 0x74, 0x63, 0x68, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x20, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x3a, 0x01, 0x2a, - 0x22, 0x15, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x62, 0x61, 0x74, 0x63, - 0x68, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x12, 0x72, 0x0a, 0x0f, 0x43, 0x61, 0x6e, 0x63, 0x65, - 0x6c, 0x41, 0x6c, 0x6c, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x12, 0x1e, 0x2e, 0x67, 0x63, 0x74, - 0x72, 0x70, 0x63, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x41, 0x6c, 0x6c, 0x4f, 0x72, 0x64, - 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x67, 0x63, 0x74, - 0x72, 0x70, 0x63, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x41, 0x6c, 0x6c, 0x4f, 0x72, 0x64, - 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1e, 0x82, 0xd3, 0xe4, - 0x93, 0x02, 0x18, 0x3a, 0x01, 0x2a, 0x22, 0x13, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x61, 0x6e, 0x63, - 0x65, 0x6c, 0x61, 0x6c, 0x6c, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x12, 0x57, 0x0a, 0x09, 0x47, - 0x65, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x18, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, - 0x63, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x45, - 0x76, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x15, 0x82, - 0xd3, 0xe4, 0x93, 0x02, 0x0f, 0x12, 0x0d, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x65, 0x76, - 0x65, 0x6e, 0x74, 0x73, 0x12, 0x56, 0x0a, 0x08, 0x41, 0x64, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, - 0x12, 0x17, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x45, 0x76, 0x65, - 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x67, 0x63, 0x74, 0x72, - 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0x17, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x11, 0x3a, 0x01, 0x2a, 0x22, 0x0c, - 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x64, 0x64, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x5e, 0x0a, 0x0b, - 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x1a, 0x2e, 0x67, 0x63, - 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, - 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x1a, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x14, 0x3a, 0x01, 0x2a, 0x22, 0x0f, 0x2f, 0x76, 0x31, - 0x2f, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x12, 0xb2, 0x01, 0x0a, - 0x21, 0x47, 0x65, 0x74, 0x43, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, - 0x63, 0x79, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, - 0x65, 0x73, 0x12, 0x30, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x43, - 0x72, 0x79, 0x70, 0x74, 0x6f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x44, 0x65, 0x70, - 0x6f, 0x73, 0x69, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, + 0x73, 0x65, 0x22, 0x1a, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x14, 0x3a, 0x01, 0x2a, 0x22, 0x0f, 0x2f, + 0x76, 0x31, 0x2f, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x12, 0xb2, + 0x01, 0x0a, 0x21, 0x47, 0x65, 0x74, 0x43, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x63, 0x75, 0x72, 0x72, + 0x65, 0x6e, 0x63, 0x79, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, + 0x73, 0x73, 0x65, 0x73, 0x12, 0x30, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x28, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x22, 0x3a, - 0x01, 0x2a, 0x22, 0x1d, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x63, 0x72, 0x79, 0x70, 0x74, - 0x6f, 0x64, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, - 0x73, 0x12, 0xaa, 0x01, 0x0a, 0x1f, 0x47, 0x65, 0x74, 0x43, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x63, - 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x41, 0x64, - 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x2e, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, - 0x65, 0x74, 0x43, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, - 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, - 0x65, 0x74, 0x43, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, - 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x26, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x20, 0x3a, 0x01, - 0x2a, 0x22, 0x1b, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x63, 0x72, 0x79, 0x70, 0x74, 0x6f, - 0x64, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x9e, - 0x01, 0x0a, 0x1a, 0x47, 0x65, 0x74, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x54, - 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x73, 0x12, 0x29, 0x2e, - 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, - 0x62, 0x6c, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x43, 0x68, 0x61, 0x69, 0x6e, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, - 0x63, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x54, 0x72, - 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x29, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x23, 0x3a, 0x01, 0x2a, 0x22, - 0x1e, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, - 0x65, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x73, 0x12, - 0x6c, 0x0a, 0x11, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x46, 0x69, 0x61, 0x74, 0x46, - 0x75, 0x6e, 0x64, 0x73, 0x12, 0x1b, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x69, - 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x46, 0x69, 0x61, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x18, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x69, 0x74, 0x68, 0x64, - 0x72, 0x61, 0x77, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x20, 0x82, 0xd3, 0xe4, - 0x93, 0x02, 0x1a, 0x3a, 0x01, 0x2a, 0x22, 0x15, 0x2f, 0x76, 0x31, 0x2f, 0x77, 0x69, 0x74, 0x68, - 0x64, 0x72, 0x61, 0x77, 0x66, 0x69, 0x61, 0x74, 0x66, 0x75, 0x6e, 0x64, 0x73, 0x12, 0x8b, 0x01, - 0x0a, 0x1b, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x43, 0x72, 0x79, 0x70, 0x74, 0x6f, - 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x46, 0x75, 0x6e, 0x64, 0x73, 0x12, 0x1d, 0x2e, - 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x43, - 0x72, 0x79, 0x70, 0x74, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x67, - 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x33, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2d, 0x3a, 0x01, - 0x2a, 0x22, 0x28, 0x2f, 0x76, 0x31, 0x2f, 0x77, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x69, - 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x63, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x66, 0x75, 0x6e, 0x64, - 0x73, 0x77, 0x66, 0x69, 0x61, 0x74, 0x66, 0x75, 0x6e, 0x64, 0x73, 0x12, 0x82, 0x01, 0x0a, 0x13, - 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x42, - 0x79, 0x49, 0x44, 0x12, 0x22, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x69, 0x74, - 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x42, 0x79, 0x49, 0x44, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, - 0x2e, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, - 0x42, 0x79, 0x49, 0x44, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x22, 0x82, 0xd3, - 0xe4, 0x93, 0x02, 0x1c, 0x3a, 0x01, 0x2a, 0x22, 0x17, 0x2f, 0x76, 0x31, 0x2f, 0x77, 0x69, 0x74, - 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x62, 0x79, 0x69, 0x64, - 0x12, 0x9d, 0x01, 0x0a, 0x1a, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x45, - 0x76, 0x65, 0x6e, 0x74, 0x73, 0x42, 0x79, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, - 0x29, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, - 0x77, 0x61, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x42, 0x79, 0x45, 0x78, 0x63, 0x68, 0x61, - 0x6e, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x67, 0x63, 0x74, - 0x72, 0x70, 0x63, 0x2e, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x45, 0x76, - 0x65, 0x6e, 0x74, 0x73, 0x42, 0x79, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x28, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x22, 0x3a, 0x01, - 0x2a, 0x22, 0x1d, 0x2f, 0x76, 0x31, 0x2f, 0x77, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, - 0x6c, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x62, 0x79, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, - 0x12, 0x91, 0x01, 0x0a, 0x16, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x45, - 0x76, 0x65, 0x6e, 0x74, 0x73, 0x42, 0x79, 0x44, 0x61, 0x74, 0x65, 0x12, 0x25, 0x2e, 0x67, 0x63, - 0x74, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x45, - 0x76, 0x65, 0x6e, 0x74, 0x73, 0x42, 0x79, 0x44, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x69, 0x74, 0x68, - 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x42, 0x79, 0x45, 0x78, - 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x24, - 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1e, 0x3a, 0x01, 0x2a, 0x22, 0x19, 0x2f, 0x76, 0x31, 0x2f, 0x77, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, + 0x47, 0x65, 0x74, 0x43, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, + 0x79, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x28, 0x82, 0xd3, 0xe4, 0x93, 0x02, + 0x22, 0x3a, 0x01, 0x2a, 0x22, 0x1d, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x63, 0x72, 0x79, + 0x70, 0x74, 0x6f, 0x64, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, + 0x73, 0x65, 0x73, 0x12, 0xaa, 0x01, 0x0a, 0x1f, 0x47, 0x65, 0x74, 0x43, 0x72, 0x79, 0x70, 0x74, + 0x6f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, + 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x2e, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, + 0x2e, 0x47, 0x65, 0x74, 0x43, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, + 0x63, 0x79, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, + 0x2e, 0x47, 0x65, 0x74, 0x43, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, + 0x63, 0x79, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x26, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x20, + 0x3a, 0x01, 0x2a, 0x22, 0x1b, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x63, 0x72, 0x79, 0x70, + 0x74, 0x6f, 0x64, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, + 0x12, 0x9e, 0x01, 0x0a, 0x1a, 0x47, 0x65, 0x74, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, + 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x73, 0x12, + 0x29, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x76, 0x61, 0x69, + 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x43, 0x68, 0x61, + 0x69, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x67, 0x63, 0x74, + 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, + 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x29, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x23, 0x3a, 0x01, + 0x2a, 0x22, 0x1e, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, + 0x62, 0x6c, 0x65, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x63, 0x68, 0x61, 0x69, 0x6e, + 0x73, 0x12, 0x6c, 0x0a, 0x11, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x46, 0x69, 0x61, + 0x74, 0x46, 0x75, 0x6e, 0x64, 0x73, 0x12, 0x1b, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, + 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x46, 0x69, 0x61, 0x74, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x69, 0x74, + 0x68, 0x64, 0x72, 0x61, 0x77, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x20, 0x82, + 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x3a, 0x01, 0x2a, 0x22, 0x15, 0x2f, 0x76, 0x31, 0x2f, 0x77, 0x69, + 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x66, 0x69, 0x61, 0x74, 0x66, 0x75, 0x6e, 0x64, 0x73, 0x12, + 0x8b, 0x01, 0x0a, 0x1b, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x43, 0x72, 0x79, 0x70, + 0x74, 0x6f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x46, 0x75, 0x6e, 0x64, 0x73, 0x12, + 0x1d, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, + 0x77, 0x43, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, + 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x33, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2d, + 0x3a, 0x01, 0x2a, 0x22, 0x28, 0x2f, 0x76, 0x31, 0x2f, 0x77, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, + 0x77, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x63, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x66, 0x75, + 0x6e, 0x64, 0x73, 0x77, 0x66, 0x69, 0x61, 0x74, 0x66, 0x75, 0x6e, 0x64, 0x73, 0x12, 0x82, 0x01, + 0x0a, 0x13, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x42, 0x79, 0x49, 0x44, 0x12, 0x22, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x57, + 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x42, 0x79, + 0x49, 0x44, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x67, 0x63, 0x74, 0x72, + 0x70, 0x63, 0x2e, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x45, 0x76, 0x65, + 0x6e, 0x74, 0x42, 0x79, 0x49, 0x44, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x22, + 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1c, 0x3a, 0x01, 0x2a, 0x22, 0x17, 0x2f, 0x76, 0x31, 0x2f, 0x77, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x62, 0x79, - 0x64, 0x61, 0x74, 0x65, 0x12, 0x73, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x67, 0x65, - 0x72, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x1f, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, - 0x63, 0x2e, 0x47, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x67, 0x65, 0x72, 0x44, 0x65, 0x74, 0x61, 0x69, - 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x67, 0x63, 0x74, 0x72, - 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x67, 0x65, 0x72, 0x44, 0x65, 0x74, 0x61, - 0x69, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1c, 0x82, 0xd3, 0xe4, - 0x93, 0x02, 0x16, 0x12, 0x14, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x6c, 0x6f, 0x67, 0x67, - 0x65, 0x72, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x76, 0x0a, 0x10, 0x53, 0x65, 0x74, - 0x4c, 0x6f, 0x67, 0x67, 0x65, 0x72, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x1f, 0x2e, - 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x67, 0x65, 0x72, - 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, - 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x67, 0x65, - 0x72, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x1f, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x3a, 0x01, 0x2a, 0x22, 0x14, 0x2f, 0x76, 0x31, - 0x2f, 0x73, 0x65, 0x74, 0x6c, 0x6f, 0x67, 0x67, 0x65, 0x72, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, - 0x73, 0x12, 0x76, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, - 0x50, 0x61, 0x69, 0x72, 0x73, 0x12, 0x1f, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, - 0x65, 0x74, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x50, 0x61, 0x69, 0x72, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, - 0x47, 0x65, 0x74, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x50, 0x61, 0x69, 0x72, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1f, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x19, - 0x3a, 0x01, 0x2a, 0x22, 0x14, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x65, 0x78, 0x63, 0x68, - 0x61, 0x6e, 0x67, 0x65, 0x70, 0x61, 0x69, 0x72, 0x73, 0x12, 0x6a, 0x0a, 0x0f, 0x53, 0x65, 0x74, - 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x50, 0x61, 0x69, 0x72, 0x12, 0x1e, 0x2e, 0x67, - 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, - 0x65, 0x50, 0x61, 0x69, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x67, - 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x18, 0x3a, 0x01, 0x2a, - 0x22, 0x13, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x65, 0x74, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, - 0x65, 0x70, 0x61, 0x69, 0x72, 0x12, 0x74, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x4f, 0x72, 0x64, 0x65, - 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x21, 0x2e, 0x67, 0x63, - 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, - 0x6b, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, - 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, - 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1e, 0x82, 0xd3, 0xe4, 0x93, 0x02, - 0x18, 0x12, 0x16, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x62, - 0x6f, 0x6f, 0x6b, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x30, 0x01, 0x12, 0x8c, 0x01, 0x0a, 0x1a, - 0x47, 0x65, 0x74, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, - 0x62, 0x6f, 0x6f, 0x6b, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x29, 0x2e, 0x67, 0x63, 0x74, - 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4f, - 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4f, - 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x26, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x20, 0x12, 0x1e, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, - 0x74, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, - 0x6f, 0x6b, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x30, 0x01, 0x12, 0x68, 0x0a, 0x0f, 0x47, 0x65, - 0x74, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x72, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x1e, 0x2e, - 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x72, - 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, - 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x72, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x15, 0x12, 0x13, 0x2f, - 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x72, 0x73, 0x74, 0x72, 0x65, - 0x61, 0x6d, 0x30, 0x01, 0x12, 0x80, 0x01, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x45, 0x78, 0x63, 0x68, - 0x61, 0x6e, 0x67, 0x65, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x72, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, - 0x12, 0x26, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x78, 0x63, - 0x68, 0x61, 0x6e, 0x67, 0x65, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x72, 0x53, 0x74, 0x72, 0x65, 0x61, - 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, - 0x63, 0x2e, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x23, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1d, 0x12, 0x1b, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, - 0x74, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x72, 0x73, - 0x74, 0x72, 0x65, 0x61, 0x6d, 0x30, 0x01, 0x12, 0x67, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x41, 0x75, - 0x64, 0x69, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x1c, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, + 0x69, 0x64, 0x12, 0x9d, 0x01, 0x0a, 0x1a, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, + 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x42, 0x79, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, + 0x65, 0x12, 0x29, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x69, 0x74, 0x68, 0x64, + 0x72, 0x61, 0x77, 0x61, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x42, 0x79, 0x45, 0x78, 0x63, + 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x67, + 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, + 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x42, 0x79, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x28, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x22, + 0x3a, 0x01, 0x2a, 0x22, 0x1d, 0x2f, 0x76, 0x31, 0x2f, 0x77, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, + 0x77, 0x61, 0x6c, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x62, 0x79, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, + 0x67, 0x65, 0x12, 0x91, 0x01, 0x0a, 0x16, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, + 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x42, 0x79, 0x44, 0x61, 0x74, 0x65, 0x12, 0x25, 0x2e, + 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, + 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x42, 0x79, 0x44, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x69, + 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x42, 0x79, + 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x24, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1e, 0x3a, 0x01, 0x2a, 0x22, 0x19, 0x2f, 0x76, 0x31, + 0x2f, 0x77, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x65, 0x76, 0x65, 0x6e, 0x74, + 0x62, 0x79, 0x64, 0x61, 0x74, 0x65, 0x12, 0x73, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x4c, 0x6f, 0x67, + 0x67, 0x65, 0x72, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x1f, 0x2e, 0x67, 0x63, 0x74, + 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x67, 0x65, 0x72, 0x44, 0x65, 0x74, + 0x61, 0x69, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x67, 0x63, + 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x67, 0x65, 0x72, 0x44, 0x65, + 0x74, 0x61, 0x69, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1c, 0x82, + 0xd3, 0xe4, 0x93, 0x02, 0x16, 0x12, 0x14, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x6c, 0x6f, + 0x67, 0x67, 0x65, 0x72, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x76, 0x0a, 0x10, 0x53, + 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x67, 0x65, 0x72, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, + 0x1f, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x67, + 0x65, 0x72, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x20, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x4c, 0x6f, 0x67, + 0x67, 0x65, 0x72, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x1f, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x3a, 0x01, 0x2a, 0x22, 0x14, 0x2f, + 0x76, 0x31, 0x2f, 0x73, 0x65, 0x74, 0x6c, 0x6f, 0x67, 0x67, 0x65, 0x72, 0x64, 0x65, 0x74, 0x61, + 0x69, 0x6c, 0x73, 0x12, 0x76, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, + 0x67, 0x65, 0x50, 0x61, 0x69, 0x72, 0x73, 0x12, 0x1f, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, + 0x2e, 0x47, 0x65, 0x74, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x50, 0x61, 0x69, 0x72, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, + 0x63, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x50, 0x61, 0x69, + 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1f, 0x82, 0xd3, 0xe4, 0x93, + 0x02, 0x19, 0x3a, 0x01, 0x2a, 0x22, 0x14, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x65, 0x78, + 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x70, 0x61, 0x69, 0x72, 0x73, 0x12, 0x6a, 0x0a, 0x0f, 0x53, + 0x65, 0x74, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x50, 0x61, 0x69, 0x72, 0x12, 0x1e, + 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x45, 0x78, 0x63, 0x68, 0x61, + 0x6e, 0x67, 0x65, 0x50, 0x61, 0x69, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, + 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x18, 0x3a, + 0x01, 0x2a, 0x22, 0x13, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x65, 0x74, 0x65, 0x78, 0x63, 0x68, 0x61, + 0x6e, 0x67, 0x65, 0x70, 0x61, 0x69, 0x72, 0x12, 0x74, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x4f, 0x72, + 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x21, 0x2e, + 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x62, + 0x6f, 0x6f, 0x6b, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x19, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x62, + 0x6f, 0x6f, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1e, 0x82, 0xd3, 0xe4, + 0x93, 0x02, 0x18, 0x12, 0x16, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x6f, 0x72, 0x64, 0x65, + 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x30, 0x01, 0x12, 0x8c, 0x01, + 0x0a, 0x1a, 0x47, 0x65, 0x74, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4f, 0x72, 0x64, + 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x29, 0x2e, 0x67, + 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, + 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, + 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x26, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x20, 0x12, 0x1e, 0x2f, 0x76, 0x31, 0x2f, + 0x67, 0x65, 0x74, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x6f, 0x72, 0x64, 0x65, 0x72, + 0x62, 0x6f, 0x6f, 0x6b, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x30, 0x01, 0x12, 0x68, 0x0a, 0x0f, + 0x47, 0x65, 0x74, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x72, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, + 0x1e, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x69, 0x63, 0x6b, + 0x65, 0x72, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x16, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x72, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x15, 0x12, + 0x13, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x72, 0x73, 0x74, + 0x72, 0x65, 0x61, 0x6d, 0x30, 0x01, 0x12, 0x80, 0x01, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x45, 0x78, + 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x72, 0x53, 0x74, 0x72, 0x65, + 0x61, 0x6d, 0x12, 0x26, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x45, + 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x72, 0x53, 0x74, 0x72, + 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x63, 0x74, + 0x72, 0x70, 0x63, 0x2e, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x23, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1d, 0x12, 0x1b, 0x2f, 0x76, 0x31, 0x2f, + 0x67, 0x65, 0x74, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x74, 0x69, 0x63, 0x6b, 0x65, + 0x72, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x30, 0x01, 0x12, 0x67, 0x0a, 0x0d, 0x47, 0x65, 0x74, + 0x41, 0x75, 0x64, 0x69, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x1c, 0x2e, 0x67, 0x63, 0x74, + 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x75, 0x64, 0x69, 0x74, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x75, 0x64, 0x69, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, - 0x47, 0x65, 0x74, 0x41, 0x75, 0x64, 0x69, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x19, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x13, 0x12, 0x11, 0x2f, - 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x61, 0x75, 0x64, 0x69, 0x74, 0x65, 0x76, 0x65, 0x6e, 0x74, - 0x12, 0x6b, 0x0a, 0x10, 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x45, 0x78, 0x65, - 0x63, 0x75, 0x74, 0x65, 0x12, 0x1f, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x43, - 0x54, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, - 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1d, - 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x12, 0x15, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x63, 0x74, 0x73, - 0x63, 0x72, 0x69, 0x70, 0x74, 0x2f, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x12, 0x6b, 0x0a, - 0x0f, 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, - 0x12, 0x1e, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, - 0x69, 0x70, 0x74, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x17, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, - 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1f, 0x82, 0xd3, 0xe4, 0x93, 0x02, - 0x19, 0x3a, 0x01, 0x2a, 0x22, 0x14, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x63, 0x74, 0x73, 0x63, 0x72, - 0x69, 0x70, 0x74, 0x2f, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x78, 0x0a, 0x13, 0x47, 0x43, - 0x54, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x52, 0x65, 0x61, 0x64, 0x53, 0x63, 0x72, 0x69, 0x70, - 0x74, 0x12, 0x22, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x43, 0x54, 0x53, 0x63, - 0x72, 0x69, 0x70, 0x74, 0x52, 0x65, 0x61, 0x64, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, - 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x3a, 0x01, 0x2a, - 0x22, 0x12, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x63, 0x74, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x2f, - 0x72, 0x65, 0x61, 0x64, 0x12, 0x70, 0x0a, 0x0f, 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, 0x70, - 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1e, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, - 0x2e, 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, - 0x2e, 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1c, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x16, - 0x12, 0x14, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x63, 0x74, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x2f, - 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x6c, 0x0a, 0x0e, 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, - 0x69, 0x70, 0x74, 0x51, 0x75, 0x65, 0x72, 0x79, 0x12, 0x1d, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, - 0x63, 0x2e, 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x51, 0x75, 0x65, 0x72, 0x79, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x19, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x13, 0x12, + 0x11, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x61, 0x75, 0x64, 0x69, 0x74, 0x65, 0x76, 0x65, + 0x6e, 0x74, 0x12, 0x6b, 0x0a, 0x10, 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x45, + 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x12, 0x1f, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, + 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, + 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x1d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x12, 0x15, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x63, + 0x74, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x2f, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x12, + 0x6b, 0x0a, 0x0f, 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x55, 0x70, 0x6c, 0x6f, + 0x61, 0x64, 0x12, 0x1e, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x43, 0x54, 0x53, + 0x63, 0x72, 0x69, 0x70, 0x74, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x65, + 0x72, 0x69, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1f, 0x82, 0xd3, 0xe4, + 0x93, 0x02, 0x19, 0x3a, 0x01, 0x2a, 0x22, 0x14, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x63, 0x74, 0x73, + 0x63, 0x72, 0x69, 0x70, 0x74, 0x2f, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x78, 0x0a, 0x13, + 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x52, 0x65, 0x61, 0x64, 0x53, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x12, 0x22, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x43, 0x54, + 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x52, 0x65, 0x61, 0x64, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x15, 0x12, - 0x13, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x63, 0x74, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x2f, 0x71, - 0x75, 0x65, 0x72, 0x79, 0x12, 0x65, 0x0a, 0x0d, 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, 0x70, - 0x74, 0x53, 0x74, 0x6f, 0x70, 0x12, 0x1c, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, - 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x53, 0x74, 0x6f, 0x70, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x6e, - 0x65, 0x72, 0x69, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1d, 0x82, 0xd3, - 0xe4, 0x93, 0x02, 0x17, 0x3a, 0x01, 0x2a, 0x22, 0x12, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x63, 0x74, - 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x2f, 0x73, 0x74, 0x6f, 0x70, 0x12, 0x6e, 0x0a, 0x10, 0x47, - 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x53, 0x74, 0x6f, 0x70, 0x41, 0x6c, 0x6c, 0x12, - 0x1f, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, - 0x70, 0x74, 0x53, 0x74, 0x6f, 0x70, 0x41, 0x6c, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x17, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, - 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x20, 0x82, 0xd3, 0xe4, 0x93, 0x02, - 0x1a, 0x3a, 0x01, 0x2a, 0x22, 0x15, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x63, 0x74, 0x73, 0x63, 0x72, - 0x69, 0x70, 0x74, 0x2f, 0x73, 0x74, 0x6f, 0x70, 0x61, 0x6c, 0x6c, 0x12, 0x73, 0x0a, 0x10, 0x47, - 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x6c, 0x12, - 0x1f, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, - 0x70, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x1f, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, - 0x69, 0x70, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x22, 0x1d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x3a, 0x01, 0x2a, 0x22, 0x12, 0x2f, 0x76, - 0x31, 0x2f, 0x67, 0x63, 0x74, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x2f, 0x6c, 0x69, 0x73, 0x74, - 0x12, 0x77, 0x0a, 0x17, 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x41, 0x75, 0x74, - 0x6f, 0x4c, 0x6f, 0x61, 0x64, 0x54, 0x6f, 0x67, 0x67, 0x6c, 0x65, 0x12, 0x20, 0x2e, 0x67, 0x63, - 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x41, 0x75, - 0x74, 0x6f, 0x4c, 0x6f, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, - 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x21, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1b, 0x3a, 0x01, - 0x2a, 0x22, 0x16, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x63, 0x74, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, - 0x2f, 0x61, 0x75, 0x74, 0x6f, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x7b, 0x0a, 0x12, 0x47, 0x65, 0x74, - 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x69, 0x63, 0x43, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x73, 0x12, - 0x21, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x48, 0x69, 0x73, 0x74, - 0x6f, 0x72, 0x69, 0x63, 0x43, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x48, - 0x69, 0x73, 0x74, 0x6f, 0x72, 0x69, 0x63, 0x43, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x18, 0x12, 0x16, - 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x69, 0x63, 0x63, - 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x73, 0x12, 0x6a, 0x0a, 0x10, 0x53, 0x65, 0x74, 0x45, 0x78, 0x63, - 0x68, 0x61, 0x6e, 0x67, 0x65, 0x41, 0x73, 0x73, 0x65, 0x74, 0x12, 0x1f, 0x2e, 0x67, 0x63, 0x74, - 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x41, - 0x73, 0x73, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x67, 0x63, - 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1c, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x16, 0x12, 0x14, 0x2f, 0x76, - 0x31, 0x2f, 0x73, 0x65, 0x74, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x61, 0x73, 0x73, - 0x65, 0x74, 0x12, 0x73, 0x0a, 0x13, 0x53, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x45, 0x78, 0x63, 0x68, - 0x61, 0x6e, 0x67, 0x65, 0x50, 0x61, 0x69, 0x72, 0x73, 0x12, 0x22, 0x2e, 0x67, 0x63, 0x74, 0x72, - 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x41, 0x6c, - 0x6c, 0x50, 0x61, 0x69, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, - 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1f, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x12, 0x17, - 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x65, 0x74, 0x61, 0x6c, 0x6c, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, - 0x67, 0x65, 0x70, 0x61, 0x69, 0x72, 0x73, 0x12, 0x8e, 0x01, 0x0a, 0x1c, 0x55, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, - 0x74, 0x65, 0x64, 0x50, 0x61, 0x69, 0x72, 0x73, 0x12, 0x2b, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, - 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, - 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x50, 0x61, 0x69, 0x72, 0x73, 0x52, 0x65, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x3a, + 0x01, 0x2a, 0x22, 0x12, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x63, 0x74, 0x73, 0x63, 0x72, 0x69, 0x70, + 0x74, 0x2f, 0x72, 0x65, 0x61, 0x64, 0x12, 0x70, 0x0a, 0x0f, 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1e, 0x2e, 0x67, 0x63, 0x74, 0x72, + 0x70, 0x63, 0x2e, 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x53, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x67, 0x63, 0x74, 0x72, + 0x70, 0x63, 0x2e, 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x53, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1c, 0x82, 0xd3, 0xe4, 0x93, + 0x02, 0x16, 0x12, 0x14, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x63, 0x74, 0x73, 0x63, 0x72, 0x69, 0x70, + 0x74, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x6c, 0x0a, 0x0e, 0x47, 0x43, 0x54, 0x53, + 0x63, 0x72, 0x69, 0x70, 0x74, 0x51, 0x75, 0x65, 0x72, 0x79, 0x12, 0x1d, 0x2e, 0x67, 0x63, 0x74, + 0x72, 0x70, 0x63, 0x2e, 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x51, 0x75, 0x65, + 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x67, 0x63, 0x74, 0x72, + 0x70, 0x63, 0x2e, 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x51, 0x75, 0x65, 0x72, + 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1b, 0x82, 0xd3, 0xe4, 0x93, 0x02, + 0x15, 0x12, 0x13, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x63, 0x74, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x2f, 0x71, 0x75, 0x65, 0x72, 0x79, 0x12, 0x65, 0x0a, 0x0d, 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x53, 0x74, 0x6f, 0x70, 0x12, 0x1c, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, + 0x2e, 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x53, 0x74, 0x6f, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, - 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x28, - 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x22, 0x12, 0x20, 0x2f, 0x76, 0x31, 0x2f, 0x75, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, - 0x74, 0x65, 0x64, 0x70, 0x61, 0x69, 0x72, 0x73, 0x12, 0x77, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x45, - 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x41, 0x73, 0x73, 0x65, 0x74, 0x73, 0x12, 0x20, 0x2e, - 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, - 0x67, 0x65, 0x41, 0x73, 0x73, 0x65, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x21, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x78, 0x63, 0x68, - 0x61, 0x6e, 0x67, 0x65, 0x41, 0x73, 0x73, 0x65, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x1d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x12, 0x15, 0x2f, 0x76, 0x31, 0x2f, - 0x67, 0x65, 0x74, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x61, 0x73, 0x73, 0x65, 0x74, - 0x73, 0x12, 0x73, 0x0a, 0x10, 0x57, 0x65, 0x62, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x47, 0x65, - 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1f, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x57, - 0x65, 0x62, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, - 0x57, 0x65, 0x62, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1c, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x16, - 0x12, 0x14, 0x2f, 0x76, 0x31, 0x2f, 0x77, 0x65, 0x62, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x67, - 0x65, 0x74, 0x69, 0x6e, 0x66, 0x6f, 0x12, 0x73, 0x0a, 0x13, 0x57, 0x65, 0x62, 0x73, 0x6f, 0x63, - 0x6b, 0x65, 0x74, 0x53, 0x65, 0x74, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x22, 0x2e, - 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x65, 0x62, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, - 0x53, 0x65, 0x74, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x17, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, - 0x69, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1f, 0x82, 0xd3, 0xe4, 0x93, - 0x02, 0x19, 0x12, 0x17, 0x2f, 0x76, 0x31, 0x2f, 0x77, 0x65, 0x62, 0x73, 0x6f, 0x63, 0x6b, 0x65, - 0x74, 0x73, 0x65, 0x74, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x97, 0x01, 0x0a, 0x19, - 0x57, 0x65, 0x62, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x47, 0x65, 0x74, 0x53, 0x75, 0x62, 0x73, - 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x28, 0x2e, 0x67, 0x63, 0x74, 0x72, - 0x70, 0x63, 0x2e, 0x57, 0x65, 0x62, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x47, 0x65, 0x74, 0x53, - 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x65, 0x62, - 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x47, 0x65, 0x74, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, - 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x25, - 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1f, 0x12, 0x1d, 0x2f, 0x76, 0x31, 0x2f, 0x77, 0x65, 0x62, 0x73, - 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x67, 0x65, 0x74, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x6d, 0x0a, 0x11, 0x57, 0x65, 0x62, 0x73, 0x6f, 0x63, 0x6b, - 0x65, 0x74, 0x53, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x12, 0x20, 0x2e, 0x67, 0x63, 0x74, - 0x72, 0x70, 0x63, 0x2e, 0x57, 0x65, 0x62, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x53, 0x65, 0x74, - 0x50, 0x72, 0x6f, 0x78, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x67, - 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x12, 0x15, 0x2f, - 0x76, 0x31, 0x2f, 0x77, 0x65, 0x62, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x65, 0x74, 0x70, - 0x72, 0x6f, 0x78, 0x79, 0x12, 0x67, 0x0a, 0x0f, 0x57, 0x65, 0x62, 0x73, 0x6f, 0x63, 0x6b, 0x65, - 0x74, 0x53, 0x65, 0x74, 0x55, 0x52, 0x4c, 0x12, 0x1e, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, - 0x2e, 0x57, 0x65, 0x62, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x53, 0x65, 0x74, 0x55, 0x52, 0x4c, + 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1d, + 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x3a, 0x01, 0x2a, 0x22, 0x12, 0x2f, 0x76, 0x31, 0x2f, 0x67, + 0x63, 0x74, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x2f, 0x73, 0x74, 0x6f, 0x70, 0x12, 0x6e, 0x0a, + 0x10, 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x53, 0x74, 0x6f, 0x70, 0x41, 0x6c, + 0x6c, 0x12, 0x1f, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x43, 0x54, 0x53, 0x63, + 0x72, 0x69, 0x70, 0x74, 0x53, 0x74, 0x6f, 0x70, 0x41, 0x6c, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x65, + 0x72, 0x69, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x20, 0x82, 0xd3, 0xe4, + 0x93, 0x02, 0x1a, 0x3a, 0x01, 0x2a, 0x22, 0x15, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x63, 0x74, 0x73, + 0x63, 0x72, 0x69, 0x70, 0x74, 0x2f, 0x73, 0x74, 0x6f, 0x70, 0x61, 0x6c, 0x6c, 0x12, 0x73, 0x0a, + 0x10, 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, + 0x6c, 0x12, 0x1f, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x43, 0x54, 0x53, 0x63, + 0x72, 0x69, 0x70, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x43, 0x54, 0x53, + 0x63, 0x72, 0x69, 0x70, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x1d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x3a, 0x01, 0x2a, 0x22, 0x12, + 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x63, 0x74, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x2f, 0x6c, 0x69, + 0x73, 0x74, 0x12, 0x77, 0x0a, 0x17, 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x41, + 0x75, 0x74, 0x6f, 0x4c, 0x6f, 0x61, 0x64, 0x54, 0x6f, 0x67, 0x67, 0x6c, 0x65, 0x12, 0x20, 0x2e, + 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x41, 0x75, 0x74, 0x6f, 0x4c, 0x6f, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x17, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x21, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1b, + 0x3a, 0x01, 0x2a, 0x22, 0x16, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x63, 0x74, 0x73, 0x63, 0x72, 0x69, + 0x70, 0x74, 0x2f, 0x61, 0x75, 0x74, 0x6f, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x7b, 0x0a, 0x12, 0x47, + 0x65, 0x74, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x69, 0x63, 0x43, 0x61, 0x6e, 0x64, 0x6c, 0x65, + 0x73, 0x12, 0x21, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x48, 0x69, + 0x73, 0x74, 0x6f, 0x72, 0x69, 0x63, 0x43, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, + 0x74, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x69, 0x63, 0x43, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x18, + 0x12, 0x16, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x69, + 0x63, 0x63, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x73, 0x12, 0x6a, 0x0a, 0x10, 0x53, 0x65, 0x74, 0x45, + 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x41, 0x73, 0x73, 0x65, 0x74, 0x12, 0x1f, 0x2e, 0x67, + 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, + 0x65, 0x41, 0x73, 0x73, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, + 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1c, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x16, 0x12, 0x14, + 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x65, 0x74, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x61, + 0x73, 0x73, 0x65, 0x74, 0x12, 0x73, 0x0a, 0x13, 0x53, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x45, 0x78, + 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x50, 0x61, 0x69, 0x72, 0x73, 0x12, 0x22, 0x2e, 0x67, 0x63, + 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, + 0x41, 0x6c, 0x6c, 0x50, 0x61, 0x69, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x17, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1f, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x19, + 0x12, 0x17, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x65, 0x74, 0x61, 0x6c, 0x6c, 0x65, 0x78, 0x63, 0x68, + 0x61, 0x6e, 0x67, 0x65, 0x70, 0x61, 0x69, 0x72, 0x73, 0x12, 0x8e, 0x01, 0x0a, 0x1c, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x53, 0x75, 0x70, 0x70, + 0x6f, 0x72, 0x74, 0x65, 0x64, 0x50, 0x61, 0x69, 0x72, 0x73, 0x12, 0x2b, 0x2e, 0x67, 0x63, 0x74, + 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, + 0x67, 0x65, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x50, 0x61, 0x69, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x1b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x15, 0x12, 0x13, 0x2f, 0x76, 0x31, 0x2f, 0x77, 0x65, - 0x62, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x65, 0x74, 0x75, 0x72, 0x6c, 0x12, 0x6a, 0x0a, - 0x0f, 0x47, 0x65, 0x74, 0x52, 0x65, 0x63, 0x65, 0x6e, 0x74, 0x54, 0x72, 0x61, 0x64, 0x65, 0x73, + 0x22, 0x28, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x22, 0x12, 0x20, 0x2f, 0x76, 0x31, 0x2f, 0x75, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x75, 0x70, 0x70, + 0x6f, 0x72, 0x74, 0x65, 0x64, 0x70, 0x61, 0x69, 0x72, 0x73, 0x12, 0x77, 0x0a, 0x11, 0x47, 0x65, + 0x74, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x41, 0x73, 0x73, 0x65, 0x74, 0x73, 0x12, + 0x20, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x78, 0x63, 0x68, + 0x61, 0x6e, 0x67, 0x65, 0x41, 0x73, 0x73, 0x65, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x21, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x78, + 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x41, 0x73, 0x73, 0x65, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x12, 0x15, 0x2f, 0x76, + 0x31, 0x2f, 0x67, 0x65, 0x74, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x61, 0x73, 0x73, + 0x65, 0x74, 0x73, 0x12, 0x73, 0x0a, 0x10, 0x57, 0x65, 0x62, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, + 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1f, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, + 0x2e, 0x57, 0x65, 0x62, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, + 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, + 0x63, 0x2e, 0x57, 0x65, 0x62, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x47, 0x65, 0x74, 0x49, 0x6e, + 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1c, 0x82, 0xd3, 0xe4, 0x93, + 0x02, 0x16, 0x12, 0x14, 0x2f, 0x76, 0x31, 0x2f, 0x77, 0x65, 0x62, 0x73, 0x6f, 0x63, 0x6b, 0x65, + 0x74, 0x67, 0x65, 0x74, 0x69, 0x6e, 0x66, 0x6f, 0x12, 0x73, 0x0a, 0x13, 0x57, 0x65, 0x62, 0x73, + 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x53, 0x65, 0x74, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, + 0x22, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x65, 0x62, 0x73, 0x6f, 0x63, 0x6b, + 0x65, 0x74, 0x53, 0x65, 0x74, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x6e, + 0x65, 0x72, 0x69, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1f, 0x82, 0xd3, + 0xe4, 0x93, 0x02, 0x19, 0x12, 0x17, 0x2f, 0x76, 0x31, 0x2f, 0x77, 0x65, 0x62, 0x73, 0x6f, 0x63, + 0x6b, 0x65, 0x74, 0x73, 0x65, 0x74, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x97, 0x01, + 0x0a, 0x19, 0x57, 0x65, 0x62, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x47, 0x65, 0x74, 0x53, 0x75, + 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x28, 0x2e, 0x67, 0x63, + 0x74, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x65, 0x62, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x47, 0x65, + 0x74, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x57, + 0x65, 0x62, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x47, 0x65, 0x74, 0x53, 0x75, 0x62, 0x73, 0x63, + 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x25, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1f, 0x12, 0x1d, 0x2f, 0x76, 0x31, 0x2f, 0x77, 0x65, + 0x62, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x67, 0x65, 0x74, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x6d, 0x0a, 0x11, 0x57, 0x65, 0x62, 0x73, 0x6f, + 0x63, 0x6b, 0x65, 0x74, 0x53, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x12, 0x20, 0x2e, 0x67, + 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x65, 0x62, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x53, + 0x65, 0x74, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, + 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x12, + 0x15, 0x2f, 0x76, 0x31, 0x2f, 0x77, 0x65, 0x62, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x65, + 0x74, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x12, 0x67, 0x0a, 0x0f, 0x57, 0x65, 0x62, 0x73, 0x6f, 0x63, + 0x6b, 0x65, 0x74, 0x53, 0x65, 0x74, 0x55, 0x52, 0x4c, 0x12, 0x1e, 0x2e, 0x67, 0x63, 0x74, 0x72, + 0x70, 0x63, 0x2e, 0x57, 0x65, 0x62, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x53, 0x65, 0x74, 0x55, + 0x52, 0x4c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x67, 0x63, 0x74, 0x72, + 0x70, 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x1b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x15, 0x12, 0x13, 0x2f, 0x76, 0x31, 0x2f, + 0x77, 0x65, 0x62, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x65, 0x74, 0x75, 0x72, 0x6c, 0x12, + 0x6a, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x52, 0x65, 0x63, 0x65, 0x6e, 0x74, 0x54, 0x72, 0x61, 0x64, + 0x65, 0x73, 0x12, 0x1d, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x53, + 0x61, 0x76, 0x65, 0x64, 0x54, 0x72, 0x61, 0x64, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x1b, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x61, 0x76, 0x65, 0x64, + 0x54, 0x72, 0x61, 0x64, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1b, + 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x15, 0x12, 0x13, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x72, + 0x65, 0x63, 0x65, 0x6e, 0x74, 0x74, 0x72, 0x61, 0x64, 0x65, 0x73, 0x12, 0x70, 0x0a, 0x11, 0x47, + 0x65, 0x74, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x69, 0x63, 0x54, 0x72, 0x61, 0x64, 0x65, 0x73, 0x12, 0x1d, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x61, 0x76, 0x65, 0x64, 0x54, 0x72, 0x61, 0x64, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x61, 0x76, 0x65, 0x64, 0x54, 0x72, - 0x61, 0x64, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1b, 0x82, 0xd3, - 0xe4, 0x93, 0x02, 0x15, 0x12, 0x13, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x72, 0x65, 0x63, - 0x65, 0x6e, 0x74, 0x74, 0x72, 0x61, 0x64, 0x65, 0x73, 0x12, 0x70, 0x0a, 0x11, 0x47, 0x65, 0x74, - 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x69, 0x63, 0x54, 0x72, 0x61, 0x64, 0x65, 0x73, 0x12, 0x1d, - 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x61, 0x76, 0x65, 0x64, - 0x54, 0x72, 0x61, 0x64, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, - 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x61, 0x76, 0x65, 0x64, 0x54, 0x72, 0x61, 0x64, - 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1d, 0x82, 0xd3, 0xe4, 0x93, - 0x02, 0x17, 0x12, 0x15, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x68, 0x69, 0x73, 0x74, 0x6f, - 0x72, 0x69, 0x63, 0x74, 0x72, 0x61, 0x64, 0x65, 0x73, 0x30, 0x01, 0x12, 0x68, 0x0a, 0x0e, 0x47, - 0x65, 0x74, 0x53, 0x61, 0x76, 0x65, 0x64, 0x54, 0x72, 0x61, 0x64, 0x65, 0x73, 0x12, 0x1d, 0x2e, - 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x61, 0x76, 0x65, 0x64, 0x54, - 0x72, 0x61, 0x64, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x67, - 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x61, 0x76, 0x65, 0x64, 0x54, 0x72, 0x61, 0x64, 0x65, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1a, 0x82, 0xd3, 0xe4, 0x93, 0x02, - 0x14, 0x12, 0x12, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x73, 0x61, 0x76, 0x65, 0x64, 0x74, - 0x72, 0x61, 0x64, 0x65, 0x73, 0x12, 0x87, 0x01, 0x0a, 0x16, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, - 0x74, 0x54, 0x72, 0x61, 0x64, 0x65, 0x73, 0x54, 0x6f, 0x43, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x73, - 0x12, 0x25, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, - 0x74, 0x54, 0x72, 0x61, 0x64, 0x65, 0x73, 0x54, 0x6f, 0x43, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x73, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, - 0x2e, 0x47, 0x65, 0x74, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x69, 0x63, 0x43, 0x61, 0x6e, 0x64, - 0x6c, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x22, 0x82, 0xd3, 0xe4, - 0x93, 0x02, 0x1c, 0x12, 0x1a, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x74, - 0x74, 0x72, 0x61, 0x64, 0x65, 0x73, 0x74, 0x6f, 0x63, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x73, 0x12, - 0x9d, 0x01, 0x0a, 0x1f, 0x46, 0x69, 0x6e, 0x64, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x53, - 0x61, 0x76, 0x65, 0x64, 0x43, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, - 0x61, 0x6c, 0x73, 0x12, 0x27, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x69, 0x6e, - 0x64, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x43, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x50, 0x65, + 0x61, 0x64, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1d, 0x82, 0xd3, + 0xe4, 0x93, 0x02, 0x17, 0x12, 0x15, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x68, 0x69, 0x73, + 0x74, 0x6f, 0x72, 0x69, 0x63, 0x74, 0x72, 0x61, 0x64, 0x65, 0x73, 0x30, 0x01, 0x12, 0x68, 0x0a, + 0x0e, 0x47, 0x65, 0x74, 0x53, 0x61, 0x76, 0x65, 0x64, 0x54, 0x72, 0x61, 0x64, 0x65, 0x73, 0x12, + 0x1d, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x61, 0x76, 0x65, + 0x64, 0x54, 0x72, 0x61, 0x64, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, + 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x61, 0x76, 0x65, 0x64, 0x54, 0x72, 0x61, + 0x64, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1a, 0x82, 0xd3, 0xe4, + 0x93, 0x02, 0x14, 0x12, 0x12, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x73, 0x61, 0x76, 0x65, + 0x64, 0x74, 0x72, 0x61, 0x64, 0x65, 0x73, 0x12, 0x87, 0x01, 0x0a, 0x16, 0x43, 0x6f, 0x6e, 0x76, + 0x65, 0x72, 0x74, 0x54, 0x72, 0x61, 0x64, 0x65, 0x73, 0x54, 0x6f, 0x43, 0x61, 0x6e, 0x64, 0x6c, + 0x65, 0x73, 0x12, 0x25, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6e, 0x76, + 0x65, 0x72, 0x74, 0x54, 0x72, 0x61, 0x64, 0x65, 0x73, 0x54, 0x6f, 0x43, 0x61, 0x6e, 0x64, 0x6c, + 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x67, 0x63, 0x74, 0x72, + 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x69, 0x63, 0x43, 0x61, + 0x6e, 0x64, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x22, 0x82, + 0xd3, 0xe4, 0x93, 0x02, 0x1c, 0x12, 0x1a, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x6f, 0x6e, 0x76, 0x65, + 0x72, 0x74, 0x74, 0x72, 0x61, 0x64, 0x65, 0x73, 0x74, 0x6f, 0x63, 0x61, 0x6e, 0x64, 0x6c, 0x65, + 0x73, 0x12, 0x9d, 0x01, 0x0a, 0x1f, 0x46, 0x69, 0x6e, 0x64, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6e, + 0x67, 0x53, 0x61, 0x76, 0x65, 0x64, 0x43, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x49, 0x6e, 0x74, 0x65, + 0x72, 0x76, 0x61, 0x6c, 0x73, 0x12, 0x27, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x46, + 0x69, 0x6e, 0x64, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x43, 0x61, 0x6e, 0x64, 0x6c, 0x65, + 0x50, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, + 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x69, 0x6e, 0x64, 0x4d, 0x69, 0x73, 0x73, + 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x25, 0x12, 0x23, 0x2f, 0x76, + 0x31, 0x2f, 0x66, 0x69, 0x6e, 0x64, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x73, 0x61, 0x76, + 0x65, 0x64, 0x63, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, + 0x73, 0x12, 0x9a, 0x01, 0x0a, 0x1e, 0x46, 0x69, 0x6e, 0x64, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6e, + 0x67, 0x53, 0x61, 0x76, 0x65, 0x64, 0x54, 0x72, 0x61, 0x64, 0x65, 0x49, 0x6e, 0x74, 0x65, 0x72, + 0x76, 0x61, 0x6c, 0x73, 0x12, 0x26, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x69, + 0x6e, 0x64, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x54, 0x72, 0x61, 0x64, 0x65, 0x50, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x69, 0x6e, 0x64, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x2b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x25, 0x12, 0x23, 0x2f, 0x76, 0x31, 0x2f, + 0x73, 0x65, 0x22, 0x2a, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x24, 0x12, 0x22, 0x2f, 0x76, 0x31, 0x2f, 0x66, 0x69, 0x6e, 0x64, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x73, 0x61, 0x76, 0x65, 0x64, - 0x63, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x73, 0x12, - 0x9a, 0x01, 0x0a, 0x1e, 0x46, 0x69, 0x6e, 0x64, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x53, - 0x61, 0x76, 0x65, 0x64, 0x54, 0x72, 0x61, 0x64, 0x65, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, - 0x6c, 0x73, 0x12, 0x26, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x69, 0x6e, 0x64, - 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x54, 0x72, 0x61, 0x64, 0x65, 0x50, 0x65, 0x72, 0x69, - 0x6f, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x67, 0x63, 0x74, - 0x72, 0x70, 0x63, 0x2e, 0x46, 0x69, 0x6e, 0x64, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x49, - 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x2a, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x24, 0x12, 0x22, 0x2f, 0x76, 0x31, 0x2f, 0x66, 0x69, - 0x6e, 0x64, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x73, 0x61, 0x76, 0x65, 0x64, 0x74, 0x72, - 0x61, 0x64, 0x65, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x73, 0x12, 0x88, 0x01, 0x0a, - 0x1a, 0x53, 0x65, 0x74, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x54, 0x72, 0x61, 0x64, - 0x65, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x12, 0x29, 0x2e, 0x67, 0x63, - 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, - 0x54, 0x72, 0x61, 0x64, 0x65, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, - 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, - 0x26, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x20, 0x12, 0x1e, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x65, 0x74, - 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x74, 0x72, 0x61, 0x64, 0x65, 0x70, 0x72, 0x6f, - 0x63, 0x65, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x12, 0x86, 0x01, 0x0a, 0x14, 0x55, 0x70, 0x73, 0x65, + 0x74, 0x72, 0x61, 0x64, 0x65, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x73, 0x12, 0x88, + 0x01, 0x0a, 0x1a, 0x53, 0x65, 0x74, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x54, 0x72, + 0x61, 0x64, 0x65, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x12, 0x29, 0x2e, + 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, + 0x67, 0x65, 0x54, 0x72, 0x61, 0x64, 0x65, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x69, 0x6e, + 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, + 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x26, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x20, 0x12, 0x1e, 0x2f, 0x76, 0x31, 0x2f, 0x73, + 0x65, 0x74, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x74, 0x72, 0x61, 0x64, 0x65, 0x70, + 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x12, 0x86, 0x01, 0x0a, 0x14, 0x55, 0x70, + 0x73, 0x65, 0x72, 0x74, 0x44, 0x61, 0x74, 0x61, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x4a, + 0x6f, 0x62, 0x12, 0x23, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x44, 0x61, 0x74, 0x61, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x4a, 0x6f, 0x62, - 0x12, 0x23, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, - 0x44, 0x61, 0x74, 0x61, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x4a, 0x6f, 0x62, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x55, - 0x70, 0x73, 0x65, 0x72, 0x74, 0x44, 0x61, 0x74, 0x61, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, - 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x23, 0x82, 0xd3, 0xe4, - 0x93, 0x02, 0x1d, 0x3a, 0x01, 0x2a, 0x22, 0x18, 0x2f, 0x76, 0x31, 0x2f, 0x75, 0x70, 0x73, 0x65, - 0x72, 0x74, 0x64, 0x61, 0x74, 0x61, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x6a, 0x6f, 0x62, - 0x12, 0x81, 0x01, 0x0a, 0x18, 0x47, 0x65, 0x74, 0x44, 0x61, 0x74, 0x61, 0x48, 0x69, 0x73, 0x74, - 0x6f, 0x72, 0x79, 0x4a, 0x6f, 0x62, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x27, 0x2e, - 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x61, 0x74, 0x61, 0x48, 0x69, - 0x73, 0x74, 0x6f, 0x72, 0x79, 0x4a, 0x6f, 0x62, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, - 0x44, 0x61, 0x74, 0x61, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x4a, 0x6f, 0x62, 0x22, 0x24, - 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1e, 0x12, 0x1c, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x64, - 0x61, 0x74, 0x61, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x6a, 0x6f, 0x62, 0x64, 0x65, 0x74, - 0x61, 0x69, 0x6c, 0x73, 0x12, 0x71, 0x0a, 0x18, 0x47, 0x65, 0x74, 0x41, 0x63, 0x74, 0x69, 0x76, - 0x65, 0x44, 0x61, 0x74, 0x61, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x4a, 0x6f, 0x62, 0x73, - 0x12, 0x16, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, - 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, + 0x2e, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x44, 0x61, 0x74, 0x61, 0x48, 0x69, 0x73, 0x74, 0x6f, + 0x72, 0x79, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x23, 0x82, + 0xd3, 0xe4, 0x93, 0x02, 0x1d, 0x3a, 0x01, 0x2a, 0x22, 0x18, 0x2f, 0x76, 0x31, 0x2f, 0x75, 0x70, + 0x73, 0x65, 0x72, 0x74, 0x64, 0x61, 0x74, 0x61, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x6a, + 0x6f, 0x62, 0x12, 0x81, 0x01, 0x0a, 0x18, 0x47, 0x65, 0x74, 0x44, 0x61, 0x74, 0x61, 0x48, 0x69, + 0x73, 0x74, 0x6f, 0x72, 0x79, 0x4a, 0x6f, 0x62, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, + 0x27, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x61, 0x74, 0x61, + 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x4a, 0x6f, 0x62, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x4a, 0x6f, 0x62, - 0x73, 0x22, 0x24, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1e, 0x12, 0x1c, 0x2f, 0x76, 0x31, 0x2f, 0x67, - 0x65, 0x74, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x64, 0x61, 0x74, 0x61, 0x68, 0x69, 0x73, 0x74, - 0x6f, 0x72, 0x79, 0x6a, 0x6f, 0x62, 0x73, 0x12, 0x85, 0x01, 0x0a, 0x19, 0x47, 0x65, 0x74, 0x44, - 0x61, 0x74, 0x61, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x4a, 0x6f, 0x62, 0x73, 0x42, 0x65, - 0x74, 0x77, 0x65, 0x65, 0x6e, 0x12, 0x28, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, - 0x65, 0x74, 0x44, 0x61, 0x74, 0x61, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x4a, 0x6f, 0x62, - 0x73, 0x42, 0x65, 0x74, 0x77, 0x65, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x17, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x48, 0x69, 0x73, - 0x74, 0x6f, 0x72, 0x79, 0x4a, 0x6f, 0x62, 0x73, 0x22, 0x25, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1f, - 0x12, 0x1d, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x64, 0x61, 0x74, 0x61, 0x68, 0x69, 0x73, - 0x74, 0x6f, 0x72, 0x79, 0x6a, 0x6f, 0x62, 0x73, 0x62, 0x65, 0x74, 0x77, 0x65, 0x65, 0x6e, 0x12, - 0x81, 0x01, 0x0a, 0x18, 0x47, 0x65, 0x74, 0x44, 0x61, 0x74, 0x61, 0x48, 0x69, 0x73, 0x74, 0x6f, - 0x72, 0x79, 0x4a, 0x6f, 0x62, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x27, 0x2e, 0x67, - 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x61, 0x74, 0x61, 0x48, 0x69, 0x73, - 0x74, 0x6f, 0x72, 0x79, 0x4a, 0x6f, 0x62, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x44, - 0x61, 0x74, 0x61, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x4a, 0x6f, 0x62, 0x22, 0x24, 0x82, - 0xd3, 0xe4, 0x93, 0x02, 0x1e, 0x12, 0x1c, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x64, 0x61, - 0x74, 0x61, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x6a, 0x6f, 0x62, 0x73, 0x75, 0x6d, 0x6d, - 0x61, 0x72, 0x79, 0x12, 0x82, 0x01, 0x0a, 0x17, 0x53, 0x65, 0x74, 0x44, 0x61, 0x74, 0x61, 0x48, - 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x4a, 0x6f, 0x62, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, - 0x26, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x44, 0x61, 0x74, 0x61, - 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x4a, 0x6f, 0x62, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, - 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x26, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x20, 0x3a, 0x01, 0x2a, 0x22, 0x1b, 0x2f, 0x76, 0x31, - 0x2f, 0x73, 0x65, 0x74, 0x64, 0x61, 0x74, 0x61, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x6a, - 0x6f, 0x62, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x9d, 0x01, 0x0a, 0x20, 0x55, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x44, 0x61, 0x74, 0x61, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x4a, 0x6f, - 0x62, 0x50, 0x72, 0x65, 0x72, 0x65, 0x71, 0x75, 0x69, 0x73, 0x69, 0x74, 0x65, 0x12, 0x2f, 0x2e, - 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x44, 0x61, 0x74, - 0x61, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x4a, 0x6f, 0x62, 0x50, 0x72, 0x65, 0x72, 0x65, - 0x71, 0x75, 0x69, 0x73, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, - 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2f, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x29, 0x3a, - 0x01, 0x2a, 0x22, 0x24, 0x2f, 0x76, 0x31, 0x2f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x61, - 0x74, 0x61, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x6a, 0x6f, 0x62, 0x70, 0x72, 0x65, 0x72, - 0x65, 0x71, 0x75, 0x69, 0x73, 0x69, 0x74, 0x65, 0x12, 0x68, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x4d, - 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x12, 0x18, 0x2e, 0x67, - 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, - 0x47, 0x65, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x22, 0x1f, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x3a, 0x01, 0x2a, 0x22, 0x14, 0x2f, 0x76, - 0x31, 0x2f, 0x67, 0x65, 0x74, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x6f, 0x72, 0x64, 0x65, - 0x72, 0x73, 0x12, 0x5f, 0x0a, 0x0b, 0x4d, 0x6f, 0x64, 0x69, 0x66, 0x79, 0x4f, 0x72, 0x64, 0x65, - 0x72, 0x12, 0x1a, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x6f, 0x64, 0x69, 0x66, - 0x79, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, - 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x6f, 0x64, 0x69, 0x66, 0x79, 0x4f, 0x72, 0x64, - 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x17, 0x82, 0xd3, 0xe4, 0x93, - 0x02, 0x11, 0x12, 0x0f, 0x2f, 0x76, 0x31, 0x2f, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x79, 0x6f, 0x72, - 0x64, 0x65, 0x72, 0x12, 0x79, 0x0a, 0x13, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x53, - 0x74, 0x61, 0x74, 0x65, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x12, 0x22, 0x2e, 0x67, 0x63, 0x74, - 0x72, 0x70, 0x63, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x53, 0x74, 0x61, 0x74, - 0x65, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, - 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, - 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1f, 0x82, - 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x12, 0x17, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x75, 0x72, 0x72, 0x65, - 0x6e, 0x63, 0x79, 0x73, 0x74, 0x61, 0x74, 0x65, 0x67, 0x65, 0x74, 0x61, 0x6c, 0x6c, 0x12, 0x76, - 0x0a, 0x14, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x53, 0x74, 0x61, 0x74, 0x65, 0x54, - 0x72, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x23, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, - 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x53, 0x74, 0x61, 0x74, 0x65, 0x54, 0x72, 0x61, - 0x64, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x67, 0x63, - 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x20, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x12, 0x18, 0x2f, 0x76, - 0x31, 0x2f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x73, 0x74, 0x61, 0x74, 0x65, 0x74, - 0x72, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x76, 0x0a, 0x14, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, - 0x63, 0x79, 0x53, 0x74, 0x61, 0x74, 0x65, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x12, 0x23, - 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, - 0x53, 0x74, 0x61, 0x74, 0x65, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x6e, - 0x65, 0x72, 0x69, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x20, 0x82, 0xd3, - 0xe4, 0x93, 0x02, 0x1a, 0x12, 0x18, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, - 0x63, 0x79, 0x73, 0x74, 0x61, 0x74, 0x65, 0x64, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x12, 0x79, - 0x0a, 0x15, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x53, 0x74, 0x61, 0x74, 0x65, 0x57, - 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x12, 0x24, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, - 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x53, 0x74, 0x61, 0x74, 0x65, 0x57, 0x69, - 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, + 0x22, 0x24, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1e, 0x12, 0x1c, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, + 0x74, 0x64, 0x61, 0x74, 0x61, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x6a, 0x6f, 0x62, 0x64, + 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x71, 0x0a, 0x18, 0x47, 0x65, 0x74, 0x41, 0x63, 0x74, + 0x69, 0x76, 0x65, 0x44, 0x61, 0x74, 0x61, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x4a, 0x6f, + 0x62, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x49, + 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x67, 0x63, 0x74, + 0x72, 0x70, 0x63, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x4a, + 0x6f, 0x62, 0x73, 0x22, 0x24, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1e, 0x12, 0x1c, 0x2f, 0x76, 0x31, + 0x2f, 0x67, 0x65, 0x74, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x64, 0x61, 0x74, 0x61, 0x68, 0x69, + 0x73, 0x74, 0x6f, 0x72, 0x79, 0x6a, 0x6f, 0x62, 0x73, 0x12, 0x85, 0x01, 0x0a, 0x19, 0x47, 0x65, + 0x74, 0x44, 0x61, 0x74, 0x61, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x4a, 0x6f, 0x62, 0x73, + 0x42, 0x65, 0x74, 0x77, 0x65, 0x65, 0x6e, 0x12, 0x28, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, + 0x2e, 0x47, 0x65, 0x74, 0x44, 0x61, 0x74, 0x61, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x4a, + 0x6f, 0x62, 0x73, 0x42, 0x65, 0x74, 0x77, 0x65, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x17, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x48, + 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x4a, 0x6f, 0x62, 0x73, 0x22, 0x25, 0x82, 0xd3, 0xe4, 0x93, + 0x02, 0x1f, 0x12, 0x1d, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x64, 0x61, 0x74, 0x61, 0x68, + 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x6a, 0x6f, 0x62, 0x73, 0x62, 0x65, 0x74, 0x77, 0x65, 0x65, + 0x6e, 0x12, 0x81, 0x01, 0x0a, 0x18, 0x47, 0x65, 0x74, 0x44, 0x61, 0x74, 0x61, 0x48, 0x69, 0x73, + 0x74, 0x6f, 0x72, 0x79, 0x4a, 0x6f, 0x62, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x27, + 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x61, 0x74, 0x61, 0x48, + 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x4a, 0x6f, 0x62, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, + 0x2e, 0x44, 0x61, 0x74, 0x61, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x4a, 0x6f, 0x62, 0x22, + 0x24, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1e, 0x12, 0x1c, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, + 0x64, 0x61, 0x74, 0x61, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x6a, 0x6f, 0x62, 0x73, 0x75, + 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x82, 0x01, 0x0a, 0x17, 0x53, 0x65, 0x74, 0x44, 0x61, 0x74, + 0x61, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x4a, 0x6f, 0x62, 0x53, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x12, 0x26, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x44, 0x61, + 0x74, 0x61, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x4a, 0x6f, 0x62, 0x53, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x67, 0x63, 0x74, 0x72, + 0x70, 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x26, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x20, 0x3a, 0x01, 0x2a, 0x22, 0x1b, 0x2f, + 0x76, 0x31, 0x2f, 0x73, 0x65, 0x74, 0x64, 0x61, 0x74, 0x61, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, + 0x79, 0x6a, 0x6f, 0x62, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x9d, 0x01, 0x0a, 0x20, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x44, 0x61, 0x74, 0x61, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, + 0x4a, 0x6f, 0x62, 0x50, 0x72, 0x65, 0x72, 0x65, 0x71, 0x75, 0x69, 0x73, 0x69, 0x74, 0x65, 0x12, + 0x2f, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x44, + 0x61, 0x74, 0x61, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x4a, 0x6f, 0x62, 0x50, 0x72, 0x65, + 0x72, 0x65, 0x71, 0x75, 0x69, 0x73, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x17, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, + 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2f, 0x82, 0xd3, 0xe4, 0x93, 0x02, + 0x29, 0x3a, 0x01, 0x2a, 0x22, 0x24, 0x2f, 0x76, 0x31, 0x2f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x64, 0x61, 0x74, 0x61, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x6a, 0x6f, 0x62, 0x70, 0x72, + 0x65, 0x72, 0x65, 0x71, 0x75, 0x69, 0x73, 0x69, 0x74, 0x65, 0x12, 0x68, 0x0a, 0x10, 0x47, 0x65, + 0x74, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x12, 0x18, + 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, + 0x63, 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x1f, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x3a, 0x01, 0x2a, 0x22, 0x14, + 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x6f, 0x72, + 0x64, 0x65, 0x72, 0x73, 0x12, 0x5f, 0x0a, 0x0b, 0x4d, 0x6f, 0x64, 0x69, 0x66, 0x79, 0x4f, 0x72, + 0x64, 0x65, 0x72, 0x12, 0x1a, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x6f, 0x64, + 0x69, 0x66, 0x79, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x1b, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x6f, 0x64, 0x69, 0x66, 0x79, 0x4f, + 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x17, 0x82, 0xd3, + 0xe4, 0x93, 0x02, 0x11, 0x12, 0x0f, 0x2f, 0x76, 0x31, 0x2f, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x79, + 0x6f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x79, 0x0a, 0x13, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, + 0x79, 0x53, 0x74, 0x61, 0x74, 0x65, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x12, 0x22, 0x2e, 0x67, + 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x53, 0x74, + 0x61, 0x74, 0x65, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x1d, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, + 0x63, 0x79, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x1f, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x12, 0x17, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x75, 0x72, + 0x72, 0x65, 0x6e, 0x63, 0x79, 0x73, 0x74, 0x61, 0x74, 0x65, 0x67, 0x65, 0x74, 0x61, 0x6c, 0x6c, + 0x12, 0x76, 0x0a, 0x14, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x53, 0x74, 0x61, 0x74, + 0x65, 0x54, 0x72, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x23, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, + 0x63, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x53, 0x74, 0x61, 0x74, 0x65, 0x54, + 0x72, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x21, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1b, 0x12, 0x19, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x20, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x12, 0x18, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x73, 0x74, 0x61, 0x74, - 0x65, 0x77, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x12, 0x82, 0x01, 0x0a, 0x18, 0x43, 0x75, - 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x53, 0x74, 0x61, 0x74, 0x65, 0x54, 0x72, 0x61, 0x64, 0x69, - 0x6e, 0x67, 0x50, 0x61, 0x69, 0x72, 0x12, 0x27, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, - 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x53, 0x74, 0x61, 0x74, 0x65, 0x54, 0x72, 0x61, - 0x64, 0x69, 0x6e, 0x67, 0x50, 0x61, 0x69, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x65, 0x74, 0x72, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x76, 0x0a, 0x14, 0x43, 0x75, 0x72, 0x72, + 0x65, 0x6e, 0x63, 0x79, 0x53, 0x74, 0x61, 0x74, 0x65, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, + 0x12, 0x23, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, + 0x63, 0x79, 0x53, 0x74, 0x61, 0x74, 0x65, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, + 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x20, + 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x12, 0x18, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x75, 0x72, 0x72, + 0x65, 0x6e, 0x63, 0x79, 0x73, 0x74, 0x61, 0x74, 0x65, 0x64, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, + 0x12, 0x79, 0x0a, 0x15, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x53, 0x74, 0x61, 0x74, + 0x65, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x12, 0x24, 0x2e, 0x67, 0x63, 0x74, 0x72, + 0x70, 0x63, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x53, 0x74, 0x61, 0x74, 0x65, + 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x24, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1e, - 0x12, 0x1c, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x73, 0x74, - 0x61, 0x74, 0x65, 0x74, 0x72, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x70, 0x61, 0x69, 0x72, 0x12, 0x9b, - 0x01, 0x0a, 0x1a, 0x47, 0x65, 0x74, 0x46, 0x75, 0x74, 0x75, 0x72, 0x65, 0x73, 0x50, 0x6f, 0x73, - 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x29, 0x2e, - 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x46, 0x75, 0x74, 0x75, 0x72, 0x65, - 0x73, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, - 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, - 0x63, 0x2e, 0x47, 0x65, 0x74, 0x46, 0x75, 0x74, 0x75, 0x72, 0x65, 0x73, 0x50, 0x6f, 0x73, 0x69, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x26, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x20, 0x12, 0x1e, 0x2f, 0x76, - 0x31, 0x2f, 0x67, 0x65, 0x74, 0x66, 0x75, 0x74, 0x75, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x73, 0x69, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x97, 0x01, 0x0a, - 0x19, 0x47, 0x65, 0x74, 0x46, 0x75, 0x74, 0x75, 0x72, 0x65, 0x73, 0x50, 0x6f, 0x73, 0x69, 0x74, - 0x69, 0x6f, 0x6e, 0x73, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x12, 0x28, 0x2e, 0x67, 0x63, 0x74, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x21, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1b, + 0x12, 0x19, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x73, 0x74, + 0x61, 0x74, 0x65, 0x77, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x12, 0x82, 0x01, 0x0a, 0x18, + 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x53, 0x74, 0x61, 0x74, 0x65, 0x54, 0x72, 0x61, + 0x64, 0x69, 0x6e, 0x67, 0x50, 0x61, 0x69, 0x72, 0x12, 0x27, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, + 0x63, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x53, 0x74, 0x61, 0x74, 0x65, 0x54, + 0x72, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x50, 0x61, 0x69, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x17, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, + 0x69, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x24, 0x82, 0xd3, 0xe4, 0x93, + 0x02, 0x1e, 0x12, 0x1c, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, + 0x73, 0x74, 0x61, 0x74, 0x65, 0x74, 0x72, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x70, 0x61, 0x69, 0x72, + 0x12, 0x9b, 0x01, 0x0a, 0x1a, 0x47, 0x65, 0x74, 0x46, 0x75, 0x74, 0x75, 0x72, 0x65, 0x73, 0x50, + 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, + 0x29, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x46, 0x75, 0x74, 0x75, + 0x72, 0x65, 0x73, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x75, 0x6d, 0x6d, + 0x61, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x46, 0x75, 0x74, 0x75, 0x72, 0x65, 0x73, 0x50, 0x6f, - 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, - 0x74, 0x46, 0x75, 0x74, 0x75, 0x72, 0x65, 0x73, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, - 0x73, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, - 0x25, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1f, 0x12, 0x1d, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, - 0x66, 0x75, 0x74, 0x75, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, - 0x6f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x12, 0x67, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6c, - 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, 0x12, 0x1c, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, + 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x26, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x20, 0x12, 0x1e, + 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x66, 0x75, 0x74, 0x75, 0x72, 0x65, 0x73, 0x70, 0x6f, + 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x97, + 0x01, 0x0a, 0x19, 0x47, 0x65, 0x74, 0x46, 0x75, 0x74, 0x75, 0x72, 0x65, 0x73, 0x50, 0x6f, 0x73, + 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x12, 0x28, 0x2e, 0x67, + 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x46, 0x75, 0x74, 0x75, 0x72, 0x65, 0x73, + 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, + 0x47, 0x65, 0x74, 0x46, 0x75, 0x74, 0x75, 0x72, 0x65, 0x73, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x25, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1f, 0x12, 0x1d, 0x2f, 0x76, 0x31, 0x2f, 0x67, + 0x65, 0x74, 0x66, 0x75, 0x74, 0x75, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x12, 0x67, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x43, + 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, 0x12, 0x1c, 0x2e, 0x67, 0x63, 0x74, 0x72, + 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, - 0x65, 0x74, 0x43, 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x19, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x13, 0x12, 0x11, 0x2f, 0x76, - 0x31, 0x2f, 0x67, 0x65, 0x74, 0x63, 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, 0x12, - 0x53, 0x0a, 0x08, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x12, 0x17, 0x2e, 0x67, 0x63, - 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x68, - 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x14, - 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0e, 0x12, 0x0c, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x68, 0x75, 0x74, - 0x64, 0x6f, 0x77, 0x6e, 0x12, 0x83, 0x01, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x54, 0x65, 0x63, 0x68, - 0x6e, 0x69, 0x63, 0x61, 0x6c, 0x41, 0x6e, 0x61, 0x6c, 0x79, 0x73, 0x69, 0x73, 0x12, 0x23, 0x2e, - 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x65, 0x63, 0x68, 0x6e, 0x69, - 0x63, 0x61, 0x6c, 0x41, 0x6e, 0x61, 0x6c, 0x79, 0x73, 0x69, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x54, - 0x65, 0x63, 0x68, 0x6e, 0x69, 0x63, 0x61, 0x6c, 0x41, 0x6e, 0x61, 0x6c, 0x79, 0x73, 0x69, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x20, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, - 0x12, 0x18, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x74, 0x65, 0x63, 0x68, 0x6e, 0x69, 0x63, - 0x61, 0x6c, 0x61, 0x6e, 0x61, 0x6c, 0x79, 0x73, 0x69, 0x73, 0x12, 0x87, 0x01, 0x0a, 0x15, 0x47, - 0x65, 0x74, 0x4d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x52, 0x61, 0x74, 0x65, 0x73, 0x48, 0x69, 0x73, - 0x74, 0x6f, 0x72, 0x79, 0x12, 0x24, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, - 0x74, 0x4d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x52, 0x61, 0x74, 0x65, 0x73, 0x48, 0x69, 0x73, 0x74, - 0x6f, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x67, 0x63, 0x74, - 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x52, 0x61, 0x74, - 0x65, 0x73, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x22, 0x21, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1b, 0x12, 0x19, 0x2f, 0x76, 0x31, 0x2f, 0x67, - 0x65, 0x74, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x72, 0x61, 0x74, 0x65, 0x73, 0x68, 0x69, 0x73, - 0x74, 0x6f, 0x72, 0x79, 0x12, 0x7c, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x6e, 0x61, 0x67, - 0x65, 0x64, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x21, 0x2e, 0x67, 0x63, 0x74, - 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x50, 0x6f, - 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, - 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, - 0x64, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x1e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x18, 0x12, 0x16, 0x2f, 0x76, 0x31, 0x2f, - 0x67, 0x65, 0x74, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, - 0x6f, 0x6e, 0x12, 0x88, 0x01, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x4d, 0x61, 0x6e, - 0x61, 0x67, 0x65, 0x64, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x25, 0x2e, - 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x4d, 0x61, 0x6e, - 0x61, 0x67, 0x65, 0x64, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, - 0x74, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x22, 0x82, 0xd3, 0xe4, 0x93, 0x02, - 0x1c, 0x12, 0x1a, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x61, 0x6c, 0x6c, 0x6d, 0x61, 0x6e, - 0x61, 0x67, 0x65, 0x64, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x6f, 0x0a, - 0x0f, 0x47, 0x65, 0x74, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x61, 0x74, 0x65, 0x73, - 0x12, 0x1e, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x46, 0x75, 0x6e, - 0x64, 0x69, 0x6e, 0x67, 0x52, 0x61, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x1f, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x46, 0x75, 0x6e, - 0x64, 0x69, 0x6e, 0x67, 0x52, 0x61, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x22, 0x1b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x15, 0x12, 0x13, 0x2f, 0x76, 0x31, 0x2f, 0x67, - 0x65, 0x74, 0x66, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x72, 0x61, 0x74, 0x65, 0x73, 0x12, 0x83, - 0x01, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x4c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x46, 0x75, 0x6e, 0x64, - 0x69, 0x6e, 0x67, 0x52, 0x61, 0x74, 0x65, 0x12, 0x23, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, - 0x2e, 0x47, 0x65, 0x74, 0x4c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, - 0x67, 0x52, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x67, - 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x4c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x46, - 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x20, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x12, 0x18, 0x2f, 0x76, 0x31, 0x2f, - 0x67, 0x65, 0x74, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x66, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, - 0x72, 0x61, 0x74, 0x65, 0x12, 0x83, 0x01, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x4f, 0x72, 0x64, 0x65, - 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x4d, 0x6f, 0x76, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x23, 0x2e, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x19, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x13, 0x12, 0x11, + 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x63, 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, + 0x6c, 0x12, 0x53, 0x0a, 0x08, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x12, 0x17, 0x2e, + 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, + 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x14, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0e, 0x12, 0x0c, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x68, + 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x12, 0x83, 0x01, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x54, 0x65, + 0x63, 0x68, 0x6e, 0x69, 0x63, 0x61, 0x6c, 0x41, 0x6e, 0x61, 0x6c, 0x79, 0x73, 0x69, 0x73, 0x12, + 0x23, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x65, 0x63, 0x68, + 0x6e, 0x69, 0x63, 0x61, 0x6c, 0x41, 0x6e, 0x61, 0x6c, 0x79, 0x73, 0x69, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, + 0x74, 0x54, 0x65, 0x63, 0x68, 0x6e, 0x69, 0x63, 0x61, 0x6c, 0x41, 0x6e, 0x61, 0x6c, 0x79, 0x73, + 0x69, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x20, 0x82, 0xd3, 0xe4, 0x93, + 0x02, 0x1a, 0x12, 0x18, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x74, 0x65, 0x63, 0x68, 0x6e, + 0x69, 0x63, 0x61, 0x6c, 0x61, 0x6e, 0x61, 0x6c, 0x79, 0x73, 0x69, 0x73, 0x12, 0x87, 0x01, 0x0a, + 0x15, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x52, 0x61, 0x74, 0x65, 0x73, 0x48, + 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x24, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, + 0x47, 0x65, 0x74, 0x4d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x52, 0x61, 0x74, 0x65, 0x73, 0x48, 0x69, + 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x67, + 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x52, + 0x61, 0x74, 0x65, 0x73, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x21, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1b, 0x12, 0x19, 0x2f, 0x76, 0x31, + 0x2f, 0x67, 0x65, 0x74, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x72, 0x61, 0x74, 0x65, 0x73, 0x68, + 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x7c, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x6e, + 0x61, 0x67, 0x65, 0x64, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x21, 0x2e, 0x67, + 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, + 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x23, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x6e, 0x61, + 0x67, 0x65, 0x64, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x18, 0x12, 0x16, 0x2f, 0x76, + 0x31, 0x2f, 0x67, 0x65, 0x74, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x70, 0x6f, 0x73, 0x69, + 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x88, 0x01, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x4d, + 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, + 0x25, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x4d, + 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, + 0x47, 0x65, 0x74, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x22, 0x82, 0xd3, 0xe4, + 0x93, 0x02, 0x1c, 0x12, 0x1a, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x61, 0x6c, 0x6c, 0x6d, + 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, + 0x6f, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x61, 0x74, + 0x65, 0x73, 0x12, 0x1e, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x46, + 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x61, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x46, + 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x61, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x1b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x15, 0x12, 0x13, 0x2f, 0x76, 0x31, + 0x2f, 0x67, 0x65, 0x74, 0x66, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x72, 0x61, 0x74, 0x65, 0x73, + 0x12, 0x83, 0x01, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x4c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x46, 0x75, + 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x61, 0x74, 0x65, 0x12, 0x23, 0x2e, 0x67, 0x63, 0x74, 0x72, + 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x4c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x46, 0x75, 0x6e, 0x64, + 0x69, 0x6e, 0x67, 0x52, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, + 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x4c, 0x61, 0x74, 0x65, 0x73, + 0x74, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x20, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x12, 0x18, 0x2f, 0x76, + 0x31, 0x2f, 0x67, 0x65, 0x74, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x66, 0x75, 0x6e, 0x64, 0x69, + 0x6e, 0x67, 0x72, 0x61, 0x74, 0x65, 0x12, 0x83, 0x01, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x4f, 0x72, + 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x4d, 0x6f, 0x76, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x12, + 0x23, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x72, 0x64, 0x65, + 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x4d, 0x6f, 0x76, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, + 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x4d, 0x6f, 0x76, 0x65, 0x6d, 0x65, + 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x20, 0x82, 0xd3, 0xe4, 0x93, + 0x02, 0x1a, 0x12, 0x18, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x6f, 0x72, 0x64, 0x65, 0x72, + 0x62, 0x6f, 0x6f, 0x6b, 0x6d, 0x6f, 0x76, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x9f, 0x01, 0x0a, + 0x1b, 0x47, 0x65, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x41, 0x6d, 0x6f, + 0x75, 0x6e, 0x74, 0x42, 0x79, 0x4e, 0x6f, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x12, 0x2a, 0x2e, 0x67, + 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, + 0x6f, 0x6b, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x79, 0x4e, 0x6f, 0x6d, 0x69, 0x6e, 0x61, + 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, + 0x63, 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x41, 0x6d, + 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x79, 0x4e, 0x6f, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x27, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x21, 0x12, 0x1f, 0x2f, + 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x61, + 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x62, 0x79, 0x6e, 0x6f, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x12, 0x9b, + 0x01, 0x0a, 0x1a, 0x47, 0x65, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x41, + 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x79, 0x49, 0x6d, 0x70, 0x61, 0x63, 0x74, 0x12, 0x29, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x62, - 0x6f, 0x6f, 0x6b, 0x4d, 0x6f, 0x76, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x4f, - 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x4d, 0x6f, 0x76, 0x65, 0x6d, 0x65, 0x6e, 0x74, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x20, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, - 0x12, 0x18, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, - 0x6f, 0x6b, 0x6d, 0x6f, 0x76, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x9f, 0x01, 0x0a, 0x1b, 0x47, - 0x65, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x41, 0x6d, 0x6f, 0x75, 0x6e, - 0x74, 0x42, 0x79, 0x4e, 0x6f, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x12, 0x2a, 0x2e, 0x67, 0x63, 0x74, - 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, - 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x79, 0x4e, 0x6f, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, - 0x47, 0x65, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x41, 0x6d, 0x6f, 0x75, - 0x6e, 0x74, 0x42, 0x79, 0x4e, 0x6f, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0x27, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x21, 0x12, 0x1f, 0x2f, 0x76, 0x31, - 0x2f, 0x67, 0x65, 0x74, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x61, 0x6d, 0x6f, - 0x75, 0x6e, 0x74, 0x62, 0x79, 0x6e, 0x6f, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x12, 0x9b, 0x01, 0x0a, - 0x1a, 0x47, 0x65, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x41, 0x6d, 0x6f, - 0x75, 0x6e, 0x74, 0x42, 0x79, 0x49, 0x6d, 0x70, 0x61, 0x63, 0x74, 0x12, 0x29, 0x2e, 0x67, 0x63, - 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, - 0x6b, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x79, 0x49, 0x6d, 0x70, 0x61, 0x63, 0x74, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, - 0x47, 0x65, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x41, 0x6d, 0x6f, 0x75, - 0x6e, 0x74, 0x42, 0x79, 0x49, 0x6d, 0x70, 0x61, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x26, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x20, 0x12, 0x1e, 0x2f, 0x76, 0x31, 0x2f, - 0x67, 0x65, 0x74, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x61, 0x6d, 0x6f, 0x75, - 0x6e, 0x74, 0x62, 0x79, 0x69, 0x6d, 0x70, 0x61, 0x63, 0x74, 0x12, 0x77, 0x0a, 0x11, 0x47, 0x65, - 0x74, 0x43, 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, 0x4d, 0x6f, 0x64, 0x65, 0x12, - 0x20, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6c, 0x6c, - 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, 0x4d, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x21, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, - 0x6c, 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, 0x4d, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x12, 0x15, 0x2f, 0x76, - 0x31, 0x2f, 0x67, 0x65, 0x74, 0x63, 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, 0x6d, - 0x6f, 0x64, 0x65, 0x12, 0x5f, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x4c, 0x65, 0x76, 0x65, 0x72, 0x61, - 0x67, 0x65, 0x12, 0x1a, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x4c, - 0x65, 0x76, 0x65, 0x72, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, - 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x4c, 0x65, 0x76, 0x65, 0x72, - 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x17, 0x82, 0xd3, 0xe4, - 0x93, 0x02, 0x11, 0x12, 0x0f, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x6c, 0x65, 0x76, 0x65, - 0x72, 0x61, 0x67, 0x65, 0x12, 0x7a, 0x0a, 0x11, 0x53, 0x65, 0x74, 0x43, 0x6f, 0x6c, 0x6c, 0x61, - 0x74, 0x65, 0x72, 0x61, 0x6c, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x20, 0x2e, 0x67, 0x63, 0x74, 0x72, - 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x43, 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, - 0x4d, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x67, 0x63, + 0x6f, 0x6f, 0x6b, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x79, 0x49, 0x6d, 0x70, 0x61, 0x63, + 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, + 0x63, 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x41, 0x6d, + 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x79, 0x49, 0x6d, 0x70, 0x61, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x26, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x20, 0x12, 0x1e, 0x2f, 0x76, + 0x31, 0x2f, 0x67, 0x65, 0x74, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x61, 0x6d, + 0x6f, 0x75, 0x6e, 0x74, 0x62, 0x79, 0x69, 0x6d, 0x70, 0x61, 0x63, 0x74, 0x12, 0x77, 0x0a, 0x11, + 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, 0x4d, 0x6f, 0x64, + 0x65, 0x12, 0x20, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, + 0x6c, 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, 0x4d, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, + 0x43, 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, 0x4d, 0x6f, 0x64, 0x65, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x12, 0x15, + 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x63, 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, + 0x6c, 0x6d, 0x6f, 0x64, 0x65, 0x12, 0x5f, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x4c, 0x65, 0x76, 0x65, + 0x72, 0x61, 0x67, 0x65, 0x12, 0x1a, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, + 0x74, 0x4c, 0x65, 0x76, 0x65, 0x72, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x1b, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x4c, 0x65, 0x76, + 0x65, 0x72, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x17, 0x82, + 0xd3, 0xe4, 0x93, 0x02, 0x11, 0x12, 0x0f, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x6c, 0x65, + 0x76, 0x65, 0x72, 0x61, 0x67, 0x65, 0x12, 0x7a, 0x0a, 0x11, 0x53, 0x65, 0x74, 0x43, 0x6f, 0x6c, + 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x20, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x43, 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, 0x72, - 0x61, 0x6c, 0x4d, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x20, - 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x3a, 0x01, 0x2a, 0x22, 0x15, 0x2f, 0x76, 0x31, 0x2f, 0x67, - 0x65, 0x74, 0x63, 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, 0x6d, 0x6f, 0x64, 0x65, - 0x12, 0x6a, 0x0a, 0x0d, 0x53, 0x65, 0x74, 0x4d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x54, 0x79, 0x70, - 0x65, 0x12, 0x1c, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x4d, 0x61, - 0x72, 0x67, 0x69, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x1d, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x4d, 0x61, 0x72, 0x67, - 0x69, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1c, - 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x16, 0x3a, 0x01, 0x2a, 0x22, 0x11, 0x2f, 0x76, 0x31, 0x2f, 0x67, - 0x65, 0x74, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x74, 0x79, 0x70, 0x65, 0x12, 0x62, 0x0a, 0x0b, - 0x53, 0x65, 0x74, 0x4c, 0x65, 0x76, 0x65, 0x72, 0x61, 0x67, 0x65, 0x12, 0x1a, 0x2e, 0x67, 0x63, - 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x4c, 0x65, 0x76, 0x65, 0x72, 0x61, 0x67, 0x65, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, - 0x2e, 0x53, 0x65, 0x74, 0x4c, 0x65, 0x76, 0x65, 0x72, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1a, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x14, 0x3a, 0x01, 0x2a, 0x22, - 0x0f, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x6c, 0x65, 0x76, 0x65, 0x72, 0x61, 0x67, 0x65, - 0x12, 0x86, 0x01, 0x0a, 0x14, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x50, 0x6f, 0x73, 0x69, 0x74, - 0x69, 0x6f, 0x6e, 0x4d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x12, 0x23, 0x2e, 0x67, 0x63, 0x74, 0x72, - 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, - 0x6e, 0x4d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, - 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x50, 0x6f, - 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x23, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1d, 0x3a, 0x01, 0x2a, 0x22, - 0x18, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, - 0x69, 0x6f, 0x6e, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x12, 0x6f, 0x0a, 0x0f, 0x47, 0x65, 0x74, - 0x4f, 0x70, 0x65, 0x6e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x65, 0x73, 0x74, 0x12, 0x1e, 0x2e, 0x67, - 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x70, 0x65, 0x6e, 0x49, 0x6e, 0x74, - 0x65, 0x72, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x67, - 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x70, 0x65, 0x6e, 0x49, 0x6e, 0x74, - 0x65, 0x72, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1b, 0x82, - 0xd3, 0xe4, 0x93, 0x02, 0x15, 0x12, 0x13, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x6f, 0x70, - 0x65, 0x6e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x65, 0x73, 0x74, 0x12, 0x7f, 0x0a, 0x13, 0x47, 0x65, - 0x74, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x54, 0x72, 0x61, 0x64, 0x65, 0x55, 0x52, - 0x4c, 0x12, 0x22, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x75, - 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x54, 0x72, 0x61, 0x64, 0x65, 0x55, 0x52, 0x4c, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, - 0x65, 0x74, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x54, 0x72, 0x61, 0x64, 0x65, 0x55, - 0x52, 0x4c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1f, 0x82, 0xd3, 0xe4, 0x93, - 0x02, 0x19, 0x12, 0x17, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x63, 0x75, 0x72, 0x72, 0x65, - 0x6e, 0x63, 0x79, 0x74, 0x72, 0x61, 0x64, 0x65, 0x75, 0x72, 0x6c, 0x42, 0x30, 0x5a, 0x2e, 0x67, - 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x74, 0x68, 0x72, 0x61, 0x73, 0x68, - 0x65, 0x72, 0x2d, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x67, 0x6f, 0x63, 0x72, 0x79, 0x70, 0x74, 0x6f, - 0x74, 0x72, 0x61, 0x64, 0x65, 0x72, 0x2f, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x61, 0x6c, 0x4d, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, + 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x43, 0x6f, 0x6c, 0x6c, 0x61, 0x74, + 0x65, 0x72, 0x61, 0x6c, 0x4d, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x20, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x3a, 0x01, 0x2a, 0x22, 0x15, 0x2f, 0x76, 0x31, + 0x2f, 0x67, 0x65, 0x74, 0x63, 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, 0x6d, 0x6f, + 0x64, 0x65, 0x12, 0x6a, 0x0a, 0x0d, 0x53, 0x65, 0x74, 0x4d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x54, + 0x79, 0x70, 0x65, 0x12, 0x1c, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, + 0x4d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x1d, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x4d, 0x61, + 0x72, 0x67, 0x69, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x1c, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x16, 0x3a, 0x01, 0x2a, 0x22, 0x11, 0x2f, 0x76, 0x31, + 0x2f, 0x67, 0x65, 0x74, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x74, 0x79, 0x70, 0x65, 0x12, 0x62, + 0x0a, 0x0b, 0x53, 0x65, 0x74, 0x4c, 0x65, 0x76, 0x65, 0x72, 0x61, 0x67, 0x65, 0x12, 0x1a, 0x2e, + 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x4c, 0x65, 0x76, 0x65, 0x72, 0x61, + 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x67, 0x63, 0x74, 0x72, + 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x4c, 0x65, 0x76, 0x65, 0x72, 0x61, 0x67, 0x65, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1a, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x14, 0x3a, 0x01, + 0x2a, 0x22, 0x0f, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x6c, 0x65, 0x76, 0x65, 0x72, 0x61, + 0x67, 0x65, 0x12, 0x86, 0x01, 0x0a, 0x14, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x50, 0x6f, 0x73, + 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x12, 0x23, 0x2e, 0x67, 0x63, + 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x50, 0x6f, 0x73, 0x69, 0x74, + 0x69, 0x6f, 0x6e, 0x4d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x24, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, + 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x23, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1d, 0x3a, 0x01, + 0x2a, 0x22, 0x18, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x70, 0x6f, 0x73, + 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x12, 0x6f, 0x0a, 0x0f, 0x47, + 0x65, 0x74, 0x4f, 0x70, 0x65, 0x6e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x65, 0x73, 0x74, 0x12, 0x1e, + 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x70, 0x65, 0x6e, 0x49, + 0x6e, 0x74, 0x65, 0x72, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, + 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x70, 0x65, 0x6e, 0x49, + 0x6e, 0x74, 0x65, 0x72, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x1b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x15, 0x12, 0x13, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, + 0x6f, 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x65, 0x73, 0x74, 0x12, 0x7f, 0x0a, 0x13, + 0x47, 0x65, 0x74, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x54, 0x72, 0x61, 0x64, 0x65, + 0x55, 0x52, 0x4c, 0x12, 0x22, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, + 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x54, 0x72, 0x61, 0x64, 0x65, 0x55, 0x52, 0x4c, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, + 0x2e, 0x47, 0x65, 0x74, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x54, 0x72, 0x61, 0x64, + 0x65, 0x55, 0x52, 0x4c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1f, 0x82, 0xd3, + 0xe4, 0x93, 0x02, 0x19, 0x12, 0x17, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x63, 0x75, 0x72, + 0x72, 0x65, 0x6e, 0x63, 0x79, 0x74, 0x72, 0x61, 0x64, 0x65, 0x75, 0x72, 0x6c, 0x42, 0x30, 0x5a, + 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x74, 0x68, 0x72, 0x61, + 0x73, 0x68, 0x65, 0x72, 0x2d, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x67, 0x6f, 0x63, 0x72, 0x79, 0x70, + 0x74, 0x6f, 0x74, 0x72, 0x61, 0x64, 0x65, 0x72, 0x2f, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/gctrpc/rpc.proto b/gctrpc/rpc.proto index 4eec26b1b25..7b35b96de9b 100644 --- a/gctrpc/rpc.proto +++ b/gctrpc/rpc.proto @@ -834,7 +834,7 @@ message WebsocketGetSubscriptionsRequest { message WebsocketSubscription { string channel = 1; - string pair = 2; + string pairs = 2; string asset = 3; string params = 4; } diff --git a/gctrpc/rpc.swagger.json b/gctrpc/rpc.swagger.json index 2229f88936d..c9c4c46fa10 100644 --- a/gctrpc/rpc.swagger.json +++ b/gctrpc/rpc.swagger.json @@ -7642,7 +7642,7 @@ "channel": { "type": "string" }, - "pair": { + "pairs": { "type": "string" }, "asset": { diff --git a/internal/testing/exchange/exchange.go b/internal/testing/exchange/exchange.go index 997b0d33e1b..85de1fea761 100644 --- a/internal/testing/exchange/exchange.go +++ b/internal/testing/exchange/exchange.go @@ -117,10 +117,10 @@ func MockWsInstance[T any, PT interface { require.NoErrorf(tb, err, "SetWebsocketURL should not error for auth: %v", auth) } - // Disable default subscriptions; Would disrupt unit tests - b.Features.Subscriptions = []*subscription.Subscription{} + // For testing we never want to use the default subscriptions; Tests of GenerateSubscriptions should be exercising it directly + b.Features.Subscriptions = subscription.List{} // Exchanges which don't support subscription conf; Can be removed when all exchanges support sub conf - b.Websocket.GenerateSubs = func() ([]subscription.Subscription, error) { return []subscription.Subscription{}, nil } + b.Websocket.GenerateSubs = func() (subscription.List, error) { return subscription.List{}, nil } err = b.Websocket.Connect() require.NoError(tb, err, "Connect should not error") @@ -191,13 +191,20 @@ func SetupWs(tb testing.TB, e exchange.IBotExchange) { } b := e.GetBase() - if !b.Websocket.IsEnabled() { + w, err := b.GetWebsocket() + if err != nil || !b.Websocket.IsEnabled() { tb.Skip("Websocket not enabled") } - if b.Websocket.IsConnected() { + if w.IsConnected() { return } - err := b.Websocket.Connect() + + // For testing we never want to use the default subscriptions; Tests of GenerateSubscriptions should be exercising it directly + b.Features.Subscriptions = subscription.List{} + // Exchanges which don't support subscription conf; Can be removed when all exchanges support sub conf + w.GenerateSubs = func() (subscription.List, error) { return subscription.List{}, nil } + + err = w.Connect() require.NoError(tb, err, "WsConnect should not error") setupWsOnce[e] = true diff --git a/internal/testing/subscriptions/subscriptions.go b/internal/testing/subscriptions/subscriptions.go new file mode 100644 index 00000000000..9e1192866c4 --- /dev/null +++ b/internal/testing/subscriptions/subscriptions.go @@ -0,0 +1,29 @@ +package subscriptionstest + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/thrasher-corp/gocryptotrader/exchanges/subscription" +) + +// Equal is a utility function to compare subscription lists and show a pretty failure message +// It overcomes the verbose depth of assert.ElementsMatch spewConfig +func Equal(tb testing.TB, a, b subscription.List) { + tb.Helper() + s, err := subscription.NewStoreFromList(a) + require.NoError(tb, err, "NewStoreFromList must not error") + added, missing := s.Diff(b) + if len(added) > 0 || len(missing) > 0 { + fail := "Differences:" + if len(added) > 0 { + fail = fail + "\n + " + strings.Join(added.Strings(), "\n + ") + } + if len(missing) > 0 { + fail = fail + "\n - " + strings.Join(missing.Strings(), "\n - ") + } + assert.Fail(tb, fail, "Subscriptions should be equal") + } +} From e16e16b4a16fa963ae86159d4d6658f0f3544216 Mon Sep 17 00:00:00 2001 From: Scott Date: Fri, 7 Jun 2024 15:56:17 +1000 Subject: [PATCH 5/7] Deribit: bug fixes, test fixes and implement GetCurrencyTradeURL (#1558) * initial * fixes WS instrument parsing, adds new funcs * lint, err restore, funding rate fixes * tightens function * fix breaking test, reimplement option string * fixes klines * enhance regex * WHOOPS * verbose whoops * exchange interval for basic too * Adds err processing, err channel, fix the others * utilises concurrent error grabber * minor shrinkage, its cold --- exchanges/deribit/deribit.go | 79 +++++- exchanges/deribit/deribit_test.go | 338 +++++++++++++++---------- exchanges/deribit/deribit_websocket.go | 204 ++++----------- exchanges/deribit/deribit_wrapper.go | 229 +++++++++-------- exchanges/kline/kline.go | 3 +- exchanges/orderbook/orderbook.go | 2 +- 6 files changed, 461 insertions(+), 394 deletions(-) diff --git a/exchanges/deribit/deribit.go b/exchanges/deribit/deribit.go index 00efc5ab5a3..f4787df6be4 100644 --- a/exchanges/deribit/deribit.go +++ b/exchanges/deribit/deribit.go @@ -28,11 +28,25 @@ type Deribit struct { exchange.Base } +var ( + // optionRegex compiles optionDecimalRegex at startup and is used to help set + // option currency lower-case d eg MATIC-USDC-3JUN24-0d64-P + optionRegex *regexp.Regexp +) + const ( deribitAPIVersion = "/api/v2" + tradeBaseURL = "https://www.deribit.com/" + tradeSpot = "spot/" + tradeFutures = "futures/" + tradeOptions = "options/" + tradeFuturesCombo = "futures-spreads/" + tradeOptionsCombo = "combos/" - // Public endpoints + perpString = "PERPETUAL" + optionDecimalRegex = `\d+(D)\d+` + // Public endpoints // Market Data getBookByCurrency = "public/get_book_summary_by_currency" getBookByInstrument = "public/get_book_summary_by_instrument" @@ -2682,10 +2696,51 @@ func (d *Deribit) StringToAssetKind(assetType string) (asset.Item, error) { } } -func guessAssetTypeFromInstrument(currencyPair currency.Pair) (asset.Item, error) { +// getAssetPairByInstrument is able to determine the asset type and currency pair +// based on the received instrument ID +func (d *Deribit) getAssetPairByInstrument(instrument string) (currency.Pair, asset.Item, error) { + if instrument == "" { + return currency.EMPTYPAIR, asset.Empty, errInvalidInstrumentName + } + + var item asset.Item + // Find the first occurrence of the delimiter and split the instrument string accordingly + parts := strings.Split(instrument, currency.DashDelimiter) + switch { + case len(parts) == 1: + if i := strings.IndexAny(instrument, currency.UnderscoreDelimiter); i == -1 { + return currency.EMPTYPAIR, asset.Empty, fmt.Errorf("%w %s", errUnsupportedInstrumentFormat, instrument) + } + item = asset.Spot + case len(parts) == 2: + item = asset.Futures + case parts[len(parts)-1] == "C" || parts[len(parts)-1] == "P": + item = asset.Options + case len(parts) >= 3: + // Check for options or other types + switch parts[1] { + case "USDC", "USDT": + item = asset.Futures + case "FS": + item = asset.FutureCombo + default: + item = asset.OptionCombo + } + default: + return currency.EMPTYPAIR, asset.Empty, fmt.Errorf("%w %s", errUnsupportedInstrumentFormat, instrument) + } + cp, err := currency.NewPairFromString(instrument) + if err != nil { + return currency.EMPTYPAIR, asset.Empty, err + } + + return cp, item, nil +} + +func getAssetFromPair(currencyPair currency.Pair) (asset.Item, error) { currencyPairString := currencyPair.String() vals := strings.Split(currencyPairString, currency.DashDelimiter) - if strings.HasSuffix(currencyPairString, "PERPETUAL") || len(vals) == 2 { + if strings.HasSuffix(currencyPairString, perpString) || len(vals) == 2 { return asset.Futures, nil } else if len(vals) == 1 { if vals = strings.Split(vals[0], currency.UnderscoreDelimiter); len(vals) == 2 { @@ -2721,7 +2776,7 @@ func guessAssetTypeFromInstrument(currencyPair currency.Pair) (asset.Item, error } func calculateTradingFee(feeBuilder *exchange.FeeBuilder) (float64, error) { - assetType, err := guessAssetTypeFromInstrument(feeBuilder.Pair) + assetType, err := getAssetFromPair(feeBuilder.Pair) if err != nil { return 0, err } @@ -2735,7 +2790,7 @@ func calculateTradingFee(feeBuilder *exchange.FeeBuilder) (float64, error) { return feeBuilder.Amount * feeBuilder.PurchasePrice * 0.0005, nil case strings.HasPrefix(feeBuilder.Pair.String(), currencyBTC), strings.HasPrefix(feeBuilder.Pair.String(), currencyETH): - if strings.HasSuffix(feeBuilder.Pair.String(), "PERPETUAL") { + if strings.HasSuffix(feeBuilder.Pair.String(), perpString) { if feeBuilder.IsMaker { return 0, nil } @@ -2786,10 +2841,16 @@ func (d *Deribit) formatFuturesTradablePair(pair currency.Pair) string { // it has both uppercase or lowercase characters, which we can not achieve with the Upper=true or Upper=false func (d *Deribit) optionPairToString(pair currency.Pair) string { subCodes := strings.Split(pair.Quote.String(), currency.DashDelimiter) - if len(subCodes) == 3 { - if match, err := regexp.MatchString(`^[a-zA-Z0-9_]*$`, subCodes[1]); match && err == nil { - subCodes[1] = strings.ToLower(subCodes[1]) + initialDelimiter := currency.DashDelimiter + if subCodes[0] == "USDC" { + initialDelimiter = currency.UnderscoreDelimiter + } + for i := range subCodes { + if match := optionRegex.MatchString(subCodes[i]); match { + subCodes[i] = strings.ToLower(subCodes[i]) + break } } - return pair.Base.String() + currency.DashDelimiter + strings.Join(subCodes, currency.DashDelimiter) + + return pair.Base.String() + initialDelimiter + strings.Join(subCodes, currency.DashDelimiter) } diff --git a/exchanges/deribit/deribit_test.go b/exchanges/deribit/deribit_test.go index b726cdee41a..84b92c7b2d2 100644 --- a/exchanges/deribit/deribit_test.go +++ b/exchanges/deribit/deribit_test.go @@ -3,7 +3,6 @@ package deribit import ( "context" "encoding/json" - "errors" "fmt" "log" "os" @@ -47,7 +46,7 @@ var ( d = &Deribit{} optionsTradablePair, optionComboTradablePair, futureComboTradablePair currency.Pair spotTradablePair = currency.NewPairWithDelimiter(currencyBTC, "USDC", "_") - futuresTradablePair = currency.NewPairWithDelimiter(currencyBTC, "PERPETUAL", "-") + futuresTradablePair = currency.NewPairWithDelimiter(currencyBTC, perpString, "-") assetTypeToPairsMap map[asset.Item]currency.Pair ) @@ -90,6 +89,39 @@ func TestMain(m *testing.M) { os.Exit(m.Run()) } +func instantiateTradablePairs() { + if err := d.UpdateTradablePairs(context.Background(), true); err != nil { + log.Fatalf("Failed to update tradable pairs. Error: %v", err) + } + + handleError := func(err error, msg string) { + if err != nil { + log.Fatalf("%s. Error: %v", msg, err) + } + } + + updateTradablePair := func(assetType asset.Item, tradablePair *currency.Pair) { + if d.CurrencyPairs.IsAssetEnabled(assetType) == nil { + pairs, err := d.GetEnabledPairs(assetType) + handleError(err, fmt.Sprintf("Failed to get enabled pairs for asset type %v", assetType)) + + if len(pairs) == 0 { + handleError(currency.ErrCurrencyPairsEmpty, fmt.Sprintf("No enabled pairs for asset type %v", assetType)) + } + + if assetType == asset.Options { + *tradablePair, err = d.FormatExchangeCurrency(pairs[0], assetType) + handleError(err, "Failed to format exchange currency for options pair") + } else { + *tradablePair = pairs[0] + } + } + } + updateTradablePair(asset.Options, &optionsTradablePair) + updateTradablePair(asset.OptionCombo, &optionComboTradablePair) + updateTradablePair(asset.FutureCombo, &futureComboTradablePair) +} + func TestUpdateTicker(t *testing.T) { t.Parallel() _, err := d.UpdateTicker(context.Background(), currency.Pair{}, asset.Margin) @@ -97,8 +129,8 @@ func TestUpdateTicker(t *testing.T) { for assetType, cp := range assetTypeToPairsMap { result, err := d.UpdateTicker(context.Background(), cp, assetType) - require.NoErrorf(t, err, "expected nil, got %v for asset type %s pair %s", err, assetType.String(), cp.String()) - require.NotNilf(t, result, "Expected result not to be nil for asset type %s pair %s", assetType.String(), cp.String()) + require.NoErrorf(t, err, "expected nil, got %v for asset type %s pair %s", err, assetType, cp) + require.NotNilf(t, result, "expected result not to be nil for asset type %s pair %s", assetType, cp) } } @@ -106,7 +138,7 @@ func TestUpdateOrderbook(t *testing.T) { t.Parallel() for assetType, cp := range assetTypeToPairsMap { result, err := d.UpdateOrderbook(context.Background(), cp, assetType) - require.NoErrorf(t, err, "%w for asset type: %v", err, assetType) + require.NoErrorf(t, err, "asset type: %v", assetType) require.NotNil(t, result) } } @@ -115,11 +147,9 @@ func TestGetHistoricTrades(t *testing.T) { t.Parallel() _, err := d.GetHistoricTrades(context.Background(), futureComboTradablePair, asset.FutureCombo, time.Now().Add(-time.Minute*10), time.Now()) require.ErrorIs(t, err, asset.ErrNotSupported) - var result []trade.Data for assetType, cp := range map[asset.Item]currency.Pair{asset.Spot: spotTradablePair, asset.Futures: futuresTradablePair} { - result, err = d.GetHistoricTrades(context.Background(), cp, assetType, time.Now().Add(-time.Minute*10), time.Now()) - require.NoErrorf(t, err, "%w asset type: %v", err, assetType) - require.NotNilf(t, result, "Expected value not to be nil for asset type: %s", err, assetType.String()) + _, err = d.GetHistoricTrades(context.Background(), cp, assetType, time.Now().Add(-time.Minute*10), time.Now()) + require.NoErrorf(t, err, "asset type: %v", assetType) } } @@ -127,8 +157,8 @@ func TestFetchRecentTrades(t *testing.T) { t.Parallel() for assetType, cp := range assetTypeToPairsMap { result, err := d.GetRecentTrades(context.Background(), cp, assetType) - require.NoErrorf(t, err, "expected nil, got %v for asset type %s pair %s", err, assetType.String(), cp.String()) - require.NotNilf(t, result, "Expected result not to be nil for asset type %s pair %s", assetType.String(), cp.String()) + require.NoErrorf(t, err, "expected nil, got %v for asset type %s pair %s", err, assetType, cp) + require.NotNilf(t, result, "expected result not to be nil for asset type %s pair %s", assetType, cp) } } @@ -191,8 +221,8 @@ func TestSubmitOrder(t *testing.T) { var info *InstrumentData for assetType, cp := range assetToPairStringMap { info, err = d.GetInstrument(context.Background(), d.formatPairString(assetType, cp)) - require.NoErrorf(t, err, "expected nil, got %v for asset type %s pair %s", err, assetType.String(), cp.String()) - require.NotNilf(t, result, "Expected result not to be nil for asset type %s pair %s", assetType.String(), cp.String()) + require.NoErrorf(t, err, "expected nil, got %v for asset type %s pair %s", err, assetType, cp) + require.NotNilf(t, result, "expected result not to be nil for asset type %s pair %s", assetType, cp) result, err = d.SubmitOrder( context.Background(), @@ -206,8 +236,8 @@ func TestSubmitOrder(t *testing.T) { Pair: cp, }, ) - require.NoErrorf(t, err, "expected nil, got %v for asset type %s pair %s", err, assetType.String(), cp.String()) - require.NotNilf(t, result, "Expected result not to be nil for asset type %s pair %s", assetType.String(), cp.String()) + require.NoErrorf(t, err, "expected nil, got %v for asset type %s pair %s", err, assetType, cp) + require.NotNilf(t, result, "expected result not to be nil for asset type %s pair %s", assetType, cp) } } @@ -228,7 +258,7 @@ func TestGetMarkPriceHistory(t *testing.T) { } { result, err = d.GetMarkPriceHistory(context.Background(), ps, time.Now().Add(-5*time.Minute), time.Now()) require.NoErrorf(t, err, "expected nil, got %v for pair %s", err, ps) - require.NotNilf(t, result, "Expected result not to be nil for pair %s", ps) + require.NotNilf(t, result, "expected result not to be nil for pair %s", ps) } } @@ -246,7 +276,7 @@ func TestWSRetrieveMarkPriceHistory(t *testing.T) { } { result, err = d.WSRetrieveMarkPriceHistory(ps, time.Now().Add(-4*time.Hour), time.Now()) require.NoErrorf(t, err, "expected %v, got %v currency pair %v", nil, err, ps) - require.NotNilf(t, result, "Expected value not to be nil for pair: %v", ps) + require.NotNilf(t, result, "expected value not to be nil for pair: %v", ps) } } @@ -290,7 +320,7 @@ func TestGetBookSummaryByInstrument(t *testing.T) { } { result, err = d.GetBookSummaryByInstrument(context.Background(), ps) require.NoErrorf(t, err, "expected nil, got %v for pair %s", err, ps) - require.NotNilf(t, result, "Expected result not to be nil for pair %s", ps) + require.NotNilf(t, result, "expected result not to be nil for pair %s", ps) } } @@ -308,7 +338,7 @@ func TestWSRetrieveBookSummaryByInstrument(t *testing.T) { } { result, err = d.WSRetrieveBookSummaryByInstrument(ps) require.NoErrorf(t, err, "expected nil, got %v for pair %s", err, ps) - require.NotNilf(t, result, "Expected result not to be nil for pair %s", ps) + require.NotNilf(t, result, "expected result not to be nil for pair %s", ps) } } @@ -503,8 +533,8 @@ func TestGetInstrumentData(t *testing.T) { var result *InstrumentData for assetType, cp := range assetTypeToPairsMap { result, err = d.GetInstrument(context.Background(), d.formatPairString(assetType, cp)) - require.NoErrorf(t, err, "expected nil, got %v for asset type %s pair %s", err, assetType.String(), cp.String()) - require.NotNilf(t, result, "Expected result not to be nil for asset type %s pair %s", assetType.String(), cp.String()) + require.NoErrorf(t, err, "expected nil, got %v for asset type %s pair %s", err, assetType, cp) + require.NotNilf(t, result, "expected result not to be nil for asset type %s pair %s", assetType, cp) } } @@ -516,8 +546,8 @@ func TestWSRetrieveInstrumentData(t *testing.T) { var result *InstrumentData for assetType, cp := range assetTypeToPairsMap { result, err = d.WSRetrieveInstrumentData(d.formatPairString(assetType, cp)) - require.NoErrorf(t, err, "expected nil, got %v for asset type %s pair %s", err, assetType.String(), cp.String()) - require.NotNilf(t, result, "Expected result not to be nil for asset type %s pair %s", assetType.String(), cp.String()) + require.NoErrorf(t, err, "expected nil, got %v for asset type %s pair %s", err, assetType, cp) + require.NotNilf(t, result, "expected result not to be nil for asset type %s pair %s", assetType, cp) } } @@ -638,7 +668,7 @@ func TestGetLastTradesByInstrument(t *testing.T) { for assetType, cp := range assetTypeToPairsMap { result, err := d.GetLastTradesByInstrument(context.Background(), d.formatPairString(assetType, cp), "30500", "31500", "desc", 0, true) require.NoErrorf(t, err, "expected %v, got %v currency asset %v pair %v", nil, err, assetType, cp) - require.NotNilf(t, result, "Expected value not to be nil for asset %v pair: %v", assetType, cp) + require.NotNilf(t, result, "expected value not to be nil for asset %v pair: %v", assetType, cp) } } @@ -650,7 +680,7 @@ func TestWSRetrieveLastTradesByInstrument(t *testing.T) { for assetType, cp := range assetTypeToPairsMap { result, err := d.WSRetrieveLastTradesByInstrument(d.formatPairString(assetType, cp), "30500", "31500", "desc", 0, true) require.NoErrorf(t, err, "expected %v, got %v currency asset %v pair %v", nil, err, assetType, cp) - require.NotNilf(t, result, "Expected value not to be nil for asset %v pair: %v", assetType, cp) + require.NotNilf(t, result, "expected value not to be nil for asset %v pair: %v", assetType, cp) } } @@ -662,7 +692,7 @@ func TestGetLastTradesByInstrumentAndTime(t *testing.T) { for assetType, cp := range assetTypeToPairsMap { result, err := d.GetLastTradesByInstrumentAndTime(context.Background(), d.formatPairString(assetType, cp), "", 0, time.Now().Add(-8*time.Hour), time.Now()) require.NoErrorf(t, err, "expected %v, got %v currency pair %v", nil, err, cp) - require.NotNilf(t, result, "Expected value not to be nil for pair: %v", cp) + require.NotNilf(t, result, "expected value not to be nil for pair: %v", cp) } } @@ -674,7 +704,7 @@ func TestWSRetrieveLastTradesByInstrumentAndTime(t *testing.T) { for assetType, cp := range assetTypeToPairsMap { result, err := d.WSRetrieveLastTradesByInstrumentAndTime(d.formatPairString(assetType, cp), "", 0, true, time.Now().Add(-8*time.Hour), time.Now()) require.NoErrorf(t, err, "expected %v, got %v currency pair %v", nil, err, cp) - require.NotNilf(t, result, "Expected value not to be nil for pair: %v", cp) + require.NotNilf(t, result, "expected value not to be nil for pair: %v", cp) } } @@ -687,7 +717,7 @@ func TestGetOrderbookData(t *testing.T) { for assetType, cp := range assetTypeToPairsMap { result, err = d.GetOrderbook(context.Background(), d.formatPairString(assetType, cp), 0) require.NoErrorf(t, err, "expected %v, got %v currency pair %v", nil, err, cp) - require.NotNilf(t, result, "Expected value not to be nil for pair: %v", cp) + require.NotNilf(t, result, "expected value not to be nil for pair: %v", cp) } } @@ -703,7 +733,7 @@ func TestWSRetrieveOrderbookData(t *testing.T) { for assetType, cp := range assetTypeToPairsMap { result, err = d.WSRetrieveOrderbookData(d.formatPairString(assetType, cp), 0) require.NoErrorf(t, err, "expected %v, got %v currency pair %v", nil, err, cp) - require.NotNilf(t, result, "Expected value not to be nil for pair: %v", cp) + require.NotNilf(t, result, "expected value not to be nil for pair: %v", cp) } } @@ -3249,8 +3279,8 @@ func TestFetchTicker(t *testing.T) { var err error for assetType, cp := range assetTypeToPairsMap { result, err = d.FetchTicker(context.Background(), cp, assetType) - require.NoErrorf(t, err, "expected nil, got %v for asset type %s pair %s", err, assetType.String(), cp.String()) - require.NotNilf(t, result, "Expected result not to be nil for asset type %s pair %s", assetType.String(), cp.String()) + require.NoErrorf(t, err, "expected nil, got %v for asset type %s pair %s", err, assetType, cp) + require.NotNilf(t, result, "expected result not to be nil for asset type %s pair %s", assetType, cp) } } @@ -3260,8 +3290,8 @@ func TestFetchOrderbook(t *testing.T) { var err error for assetType, cp := range assetTypeToPairsMap { result, err = d.FetchOrderbook(context.Background(), cp, assetType) - require.NoErrorf(t, err, "expected nil, got %v for asset type %s", err, assetType.String()) - require.NotNilf(t, result, "Expected result not to be nil for asset type %s", assetType.String()) + require.NoErrorf(t, err, "expected nil, got %v for asset type %s", err, assetType) + require.NotNilf(t, result, "expected result not to be nil for asset type %s", assetType) } } @@ -3279,8 +3309,8 @@ func TestFetchAccountInfo(t *testing.T) { assetTypes := d.GetAssetTypes(true) for _, assetType := range assetTypes { result, err := d.FetchAccountInfo(context.Background(), assetType) - require.NoErrorf(t, err, "expected nil, got %v for asset type %s", err, assetType.String()) - require.NotNilf(t, result, "Expected result not to be nil for asset type %s", assetType.String()) + require.NoErrorf(t, err, "expected nil, got %v for asset type %s", err, assetType) + require.NotNilf(t, result, "expected result not to be nil for asset type %s", assetType) } } @@ -3306,8 +3336,8 @@ func TestGetRecentTrades(t *testing.T) { var err error for assetType, cp := range assetTypeToPairsMap { result, err = d.GetRecentTrades(context.Background(), cp, assetType) - require.NoErrorf(t, err, "expected nil, got %v for asset type %s pair %s", err, assetType.String(), cp.String()) - require.NotNilf(t, result, "Expected result not to be nil for asset type %s pair %s", assetType.String(), cp.String()) + require.NoErrorf(t, err, "expected nil, got %v for asset type %s pair %s", err, assetType, cp) + require.NotNilf(t, result, "expected result not to be nil for asset type %s pair %s", assetType, cp) } } @@ -3337,8 +3367,8 @@ func TestCancelAllOrders(t *testing.T) { orderCancellation.AssetType = assetType orderCancellation.Pair = cp result, err = d.CancelAllOrders(context.Background(), orderCancellation) - require.NoErrorf(t, err, "expected nil, got %v for asset type %s pair %s", err, assetType.String(), cp.String()) - require.NotNilf(t, result, "Expected result not to be nil for asset type %s pair %s", assetType.String(), cp.String()) + require.NoErrorf(t, err, "expected nil, got %v for asset type %s pair %s", err, assetType, cp) + require.NotNilf(t, result, "expected result not to be nil for asset type %s pair %s", assetType, cp) } } @@ -3347,8 +3377,8 @@ func TestGetOrderInfo(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, d) for assetType, cp := range assetTypeToPairsMap { result, err := d.GetOrderInfo(context.Background(), "1234", cp, assetType) - require.NoErrorf(t, err, "expected nil, got %v for asset type %s pair %s", err, assetType.String(), cp.String()) - require.NotNilf(t, result, "Expected result not to be nil for asset type %s pair %s", assetType.String(), cp.String()) + require.NoErrorf(t, err, "expected nil, got %v for asset type %s pair %s", err, assetType, cp) + require.NotNilf(t, result, "expected result not to be nil for asset type %s pair %s", assetType, cp) } } @@ -3389,8 +3419,8 @@ func TestGetActiveOrders(t *testing.T) { getOrdersRequest.Pairs = []currency.Pair{cp} getOrdersRequest.AssetType = assetType result, err := d.GetActiveOrders(context.Background(), &getOrdersRequest) - require.NoErrorf(t, err, "expected nil, got %v for asset type %s pair %s", err, assetType.String(), cp.String()) - require.NotNilf(t, result, "Expected result not to be nil for asset type %s pair %s", assetType.String(), cp.String()) + require.NoErrorf(t, err, "expected nil, got %v for asset type %s pair %s", err, assetType, cp) + require.NotNilf(t, result, "expected result not to be nil for asset type %s pair %s", assetType, cp) } } @@ -3402,24 +3432,24 @@ func TestGetOrderHistory(t *testing.T) { Type: order.AnyType, AssetType: assetType, Side: order.AnySide, Pairs: []currency.Pair{cp}, }) - require.NoErrorf(t, err, "expected nil, got %v for asset type %s pair %s", err, assetType.String(), cp.String()) - require.NotNilf(t, result, "Expected result not to be nil for asset type %s pair %s", assetType.String(), cp.String()) + require.NoErrorf(t, err, "expected nil, got %v for asset type %s pair %s", err, assetType, cp) + require.NotNilf(t, result, "expected result not to be nil for asset type %s pair %s", assetType, cp) } } -func TestGuessAssetTypeFromInstrument(t *testing.T) { +func TestGetAssetFromPair(t *testing.T) { var assetTypeNew asset.Item for _, assetType := range []asset.Item{asset.Spot, asset.Futures, asset.Options, asset.OptionCombo, asset.FutureCombo} { availablePairs, err := d.GetEnabledPairs(assetType) - require.NoErrorf(t, err, "expected nil, got %v for asset type %s", err, assetType.String()) - require.NotNilf(t, availablePairs, "Expected result not to be nil for asset type %s", assetType.String()) + require.NoErrorf(t, err, "expected nil, got %v for asset type %s", err, assetType) + require.NotNilf(t, availablePairs, "expected result not to be nil for asset type %s", assetType) format, err := d.GetPairFormat(assetType, true) require.NoError(t, err) for id, cp := range availablePairs { t.Run(strconv.Itoa(id), func(t *testing.T) { - assetTypeNew, err = guessAssetTypeFromInstrument(cp.Format(format)) + assetTypeNew, err = getAssetFromPair(cp.Format(format)) require.Equalf(t, assetType, assetTypeNew, "expected %s, but found %s for pair string %s", assetType.String(), assetTypeNew.String(), cp.Format(format)) }) } @@ -3427,10 +3457,38 @@ func TestGuessAssetTypeFromInstrument(t *testing.T) { cp, err := currency.NewPairFromString("some_thing_else") require.NoError(t, err) - _, err = guessAssetTypeFromInstrument(cp) + _, err = getAssetFromPair(cp) assert.ErrorIs(t, err, errUnsupportedInstrumentFormat) } +func TestGetAssetPairByInstrument(t *testing.T) { + t.Parallel() + for _, assetType := range []asset.Item{asset.Spot, asset.Futures, asset.Options, asset.OptionCombo, asset.FutureCombo} { + availablePairs, err := d.GetAvailablePairs(assetType) + require.NoErrorf(t, err, "expected nil, got %v for asset type %s", err, assetType) + require.NotNilf(t, availablePairs, "expected result not to be nil for asset type %s", assetType) + for _, cp := range availablePairs { + t.Run(fmt.Sprintf("%s %s", assetType, cp), func(t *testing.T) { + t.Parallel() + extractedPair, extractedAsset, err := d.getAssetPairByInstrument(cp.String()) + assert.NoError(t, err) + assert.Equal(t, cp.String(), extractedPair.String()) + assert.Equal(t, assetType.String(), extractedAsset.String()) + }) + } + } + t.Run("empty asset, empty pair", func(t *testing.T) { + t.Parallel() + _, _, err := d.getAssetPairByInstrument("") + assert.ErrorIs(t, err, errInvalidInstrumentName) + }) + t.Run("thisIsAFakeCurrency", func(t *testing.T) { + t.Parallel() + _, _, err := d.getAssetPairByInstrument("thisIsAFakeCurrency") + assert.ErrorIs(t, err, errUnsupportedInstrumentFormat) + }) +} + func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { var feeBuilder = &exchange.FeeBuilder{ Amount: 1, @@ -3445,9 +3503,9 @@ func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { require.NoError(t, err) require.NotNil(t, result) if !sharedtestvalues.AreAPICredentialsSet(d) { - assert.Equalf(t, exchange.OfflineTradeFee, feeBuilder.FeeType, "Expected %f, received %f", exchange.OfflineTradeFee, feeBuilder.FeeType) + assert.Equalf(t, exchange.OfflineTradeFee, feeBuilder.FeeType, "expected %v, received %v", exchange.OfflineTradeFee, feeBuilder.FeeType) } else { - assert.Equalf(t, exchange.CryptocurrencyTradeFee, feeBuilder.FeeType, "Expected %v, received %v", exchange.CryptocurrencyTradeFee, feeBuilder.FeeType) + assert.Equalf(t, exchange.CryptocurrencyTradeFee, feeBuilder.FeeType, "expected %v, received %v", exchange.CryptocurrencyTradeFee, feeBuilder.FeeType) } } @@ -3594,24 +3652,47 @@ var websocketPushData = map[string]string{ func TestProcessPushData(t *testing.T) { t.Parallel() - for x := range websocketPushData { - err := d.wsHandleData([]byte(websocketPushData[x])) - require.NoErrorf(t, err, "%s: Received unexpected error for", x) + for k, v := range websocketPushData { + t.Run(k, func(t *testing.T) { + t.Parallel() + err := d.wsHandleData([]byte(v)) + require.NoErrorf(t, err, "%s: Received unexpected error for", k) + }) } } func TestFormatFuturesTradablePair(t *testing.T) { t.Parallel() futuresInstrumentsOutputList := map[currency.Pair]string{ - {Delimiter: currency.DashDelimiter, Base: currency.BTC, Quote: currency.NewCode("PERPETUAL")}: "BTC-PERPETUAL", + {Delimiter: currency.DashDelimiter, Base: currency.BTC, Quote: currency.NewCode(perpString)}: "BTC-PERPETUAL", {Delimiter: currency.DashDelimiter, Base: currency.AVAX, Quote: currency.NewCode("USDC-PERPETUAL")}: "AVAX_USDC-PERPETUAL", {Delimiter: currency.DashDelimiter, Base: currency.ETH, Quote: currency.NewCode("30DEC22")}: "ETH-30DEC22", {Delimiter: currency.DashDelimiter, Base: currency.SOL, Quote: currency.NewCode("30DEC22")}: "SOL-30DEC22", {Delimiter: currency.DashDelimiter, Base: currency.NewCode("BTCDVOL"), Quote: currency.NewCode("USDC-28JUN23")}: "BTCDVOL_USDC-28JUN23", } for pair, instrumentID := range futuresInstrumentsOutputList { - instrument := d.formatFuturesTradablePair(pair) - require.Equal(t, instrumentID, instrument) + t.Run(instrumentID, func(t *testing.T) { + t.Parallel() + instrument := d.formatFuturesTradablePair(pair) + require.Equal(t, instrumentID, instrument) + }) + } +} + +func TestOptionPairToString(t *testing.T) { + t.Parallel() + optionsList := map[currency.Pair]string{ + {Delimiter: currency.DashDelimiter, Base: currency.BTC, Quote: currency.NewCode("30MAY24-61000-C")}: "BTC-30MAY24-61000-C", + {Delimiter: currency.DashDelimiter, Base: currency.ETH, Quote: currency.NewCode("1JUN24-3200-P")}: "ETH-1JUN24-3200-P", + {Delimiter: currency.DashDelimiter, Base: currency.SOL, Quote: currency.NewCode("USDC-31MAY24-162-P")}: "SOL_USDC-31MAY24-162-P", + {Delimiter: currency.DashDelimiter, Base: currency.MATIC, Quote: currency.NewCode("USDC-6APR24-0d98-P")}: "MATIC_USDC-6APR24-0d98-P", + } + for pair, instrumentID := range optionsList { + t.Run(instrumentID, func(t *testing.T) { + t.Parallel() + instrument := d.optionPairToString(pair) + require.Equal(t, instrumentID, instrument) + }) } } @@ -3625,39 +3706,6 @@ func TestWSRetrieveCombos(t *testing.T) { assert.NotNil(t, result) } -func instantiateTradablePairs() { - if err := d.UpdateTradablePairs(context.Background(), true); err != nil { - log.Fatalf("Failed to update tradable pairs. Error: %v", err) - } - - handleError := func(err error, msg string) { - if err != nil { - log.Fatalf("%s. Error: %v", msg, err) - } - } - - updateTradablePair := func(assetType asset.Item, tradablePair *currency.Pair) { - if d.CurrencyPairs.IsAssetEnabled(assetType) == nil { - pairs, err := d.GetEnabledPairs(assetType) - handleError(err, fmt.Sprintf("Failed to get enabled pairs for asset type %v", assetType)) - - if len(pairs) == 0 { - handleError(currency.ErrCurrencyPairsEmpty, fmt.Sprintf("No enabled pairs for asset type %v", assetType)) - } - - if assetType == asset.Options { - *tradablePair, err = d.FormatExchangeCurrency(pairs[0], assetType) - handleError(err, "Failed to format exchange currency for options pair") - } else { - *tradablePair = pairs[0] - } - } - } - updateTradablePair(asset.Options, &optionsTradablePair) - updateTradablePair(asset.OptionCombo, &optionComboTradablePair) - updateTradablePair(asset.FutureCombo, &futureComboTradablePair) -} - func TestGetLatestFundingRates(t *testing.T) { t.Parallel() _, err := d.GetLatestFundingRates(context.Background(), &fundingrate.LatestRateRequest{ @@ -3799,6 +3847,9 @@ func TestGetFuturesContractDetails(t *testing.T) { result, err := d.GetFuturesContractDetails(context.Background(), asset.Futures) require.NoError(t, err) assert.NotNil(t, result) + + _, err = d.GetFuturesContractDetails(context.Background(), asset.FutureCombo) + require.ErrorIs(t, err, asset.ErrNotSupported) } func TestGetFuturesPositionSummary(t *testing.T) { @@ -3817,7 +3868,7 @@ func TestGetFuturesPositionSummary(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, d) req := &futures.PositionSummaryRequest{ Asset: asset.Futures, - Pair: currency.NewPair(currency.BTC, currency.NewCode("PERPETUAL")), + Pair: currency.NewPair(currency.BTC, currency.NewCode(perpString)), } result, err := d.GetFuturesPositionSummary(context.Background(), req) require.NoError(t, err) @@ -3834,23 +3885,32 @@ func TestGetOpenInterest(t *testing.T) { require.ErrorIs(t, err, asset.ErrNotSupported) _, err = d.GetOpenInterest(context.Background(), key.PairAsset{ - Base: currency.BTC.Item, + Base: optionsTradablePair.Base.Item, Quote: optionsTradablePair.Quote.Item, Asset: asset.Options, }) - require.True(t, err == nil || errors.Is(err, currency.ErrCurrencyNotFound)) - - var result []futures.OpenInterest - assetTypeToPairs := getAssetToPairMap(asset.Futures & asset.FutureCombo) - for assetType, cp := range assetTypeToPairs { - result, err = d.GetOpenInterest(context.Background(), key.PairAsset{ - Base: cp.Base.Item, - Quote: cp.Quote.Item, - Asset: assetType, - }) - require.NoErrorf(t, err, "expected nil, got %s for asset type %s pair %s", assetType.String(), cp.String()) - require.NotNilf(t, result, "Expected result not to be nil for asset type %s pair %s", assetType.String(), cp.String()) - } + require.NoError(t, err) + + _, err = d.GetOpenInterest(context.Background(), key.PairAsset{ + Base: currency.BTC.Item, + Quote: currency.NewCode(perpString).Item, + Asset: asset.Futures, + }) + require.NoError(t, err) + + _, err = d.GetOpenInterest(context.Background(), key.PairAsset{ + Base: currency.NewCode("XRP").Item, + Quote: currency.NewCode("USDC-PERPETUAL").Item, + Asset: asset.Futures, + }) + require.NoError(t, err) + + _, err = d.GetOpenInterest(context.Background(), key.PairAsset{ + Base: futureComboTradablePair.Base.Item, + Quote: futureComboTradablePair.Quote.Item, + Asset: asset.FutureCombo, + }) + require.NoError(t, err) } func TestIsPerpetualFutureCurrency(t *testing.T) { @@ -3861,26 +3921,27 @@ func TestIsPerpetualFutureCurrency(t *testing.T) { Response bool }{ asset.Spot: { - {Pair: currency.EMPTYPAIR, Error: futures.ErrNotPerpetualFuture}, + {Pair: currency.EMPTYPAIR, Error: currency.ErrCurrencyPairEmpty, Response: false}, + {Pair: spotTradablePair, Error: nil, Response: false}, }, asset.Futures: { - {Pair: currency.EMPTYPAIR, Error: currency.ErrCurrencyPairEmpty}, - {Pair: currency.NewPair(currency.BTC, currency.NewCode("PERPETUAL")), Response: true}, - {Pair: currency.NewPair(currency.NewCode("ETH"), currency.NewCode("FS-30DEC22_PERP")), Response: true}, + {Pair: currency.NewPair(currency.BTC, currency.NewCode(perpString)), Response: true}, }, asset.FutureCombo: { - {Pair: currency.NewPair(currency.NewCode("SOL"), currency.NewCode("FS-30DEC22_28OCT22"))}, + {Pair: currency.NewPair(currency.NewCode("BTC"), currency.NewCode("FS-27SEP24_PERP")), Response: false}, }, asset.OptionCombo: { - {Pair: currency.NewPair(currency.NewCode(currencyBTC), currency.NewCode("STRG-21OCT22")), Error: futures.ErrNotPerpetualFuture}, - {Pair: currency.EMPTYPAIR, Error: futures.ErrNotPerpetualFuture}, + {Pair: currency.NewPair(currency.NewCode(currencyBTC), currency.NewCode("STRG-21OCT22")), Error: nil, Response: false}, }, } for assetType, instances := range assetPairToErrorMap { for i := range instances { - is, err := d.IsPerpetualFutureCurrency(assetType, instances[i].Pair) - require.ErrorIsf(t, err, instances[i].Error, "expected %v, got %v for asset: %s pair: %s", instances[i].Error, err, assetType.String(), instances[i].Pair.String()) - require.Equalf(t, is, instances[i].Response, "expected %v, got %v for asset: %s pair: %s", instances[i].Response, is, assetType.String(), instances[i].Pair.String()) + t.Run(fmt.Sprintf("Asset: %s Pair: %s", assetType.String(), instances[i].Pair.String()), func(t *testing.T) { + t.Parallel() + is, err := d.IsPerpetualFutureCurrency(assetType, instances[i].Pair) + require.ErrorIsf(t, err, instances[i].Error, "expected %v, got %v for asset: %s pair: %s", instances[i].Error, err, assetType.String(), instances[i].Pair.String()) + require.Equalf(t, is, instances[i].Response, "expected %v, got %v for asset: %s pair: %s", instances[i].Response, is, assetType.String(), instances[i].Pair.String()) + }) } } } @@ -3952,24 +4013,14 @@ func TestGetResolutionFromInterval(t *testing.T) { } } -func getAssetToPairMap(items asset.Item) map[asset.Item]currency.Pair { - newMap := make(map[asset.Item]currency.Pair) - for a := range assetTypeToPairsMap { - if a&items == a { - newMap[a] = assetTypeToPairsMap[a] - } - } - return newMap -} - func TestGetValidatedCurrencyCode(t *testing.T) { t.Parallel() pairs := map[currency.Pair]string{ currency.NewPairWithDelimiter(currencySOL, "21OCT22-20-C", "-"): currencySOL, - currency.NewPairWithDelimiter(currencyBTC, "PERPETUAL", "-"): currencyBTC, - currency.NewPairWithDelimiter(currencyETH, "PERPETUAL", "-"): currencyETH, - currency.NewPairWithDelimiter(currencySOL, "PERPETUAL", "-"): currencySOL, - currency.NewPairWithDelimiter("AVAX_USDC", "PERPETUAL", "-"): currencyUSDC, + currency.NewPairWithDelimiter(currencyBTC, perpString, "-"): currencyBTC, + currency.NewPairWithDelimiter(currencyETH, perpString, "-"): currencyETH, + currency.NewPairWithDelimiter(currencySOL, perpString, "-"): currencySOL, + currency.NewPairWithDelimiter("AVAX_USDC", perpString, "-"): currencyUSDC, currency.NewPairWithDelimiter(currencyBTC, "USDC", "_"): currencyBTC, currency.NewPairWithDelimiter(currencyETH, "USDC", "_"): currencyETH, currency.NewPairWithDelimiter("DOT", "USDC-PERPETUAL", "_"): currencyUSDC, @@ -3981,3 +4032,30 @@ func TestGetValidatedCurrencyCode(t *testing.T) { require.Equal(t, pairs[x], result, "expected: %s actual : %s for currency pair: %v", x, result, pairs[x]) } } + +func TestGetCurrencyTradeURL(t *testing.T) { + t.Parallel() + _, err := d.GetCurrencyTradeURL(context.Background(), asset.Spot, currency.EMPTYPAIR) + require.ErrorIs(t, err, currency.ErrCurrencyPairEmpty) + + for _, a := range d.GetAssetTypes(false) { + var pairs currency.Pairs + pairs, err = d.CurrencyPairs.GetPairs(a, false) + require.NoError(t, err, "cannot get pairs for %s", a) + require.NotEmpty(t, pairs, "no pairs for %s", a) + var resp string + resp, err = d.GetCurrencyTradeURL(context.Background(), a, pairs[0]) + require.NoError(t, err) + assert.NotEmpty(t, resp) + } + // specific test to ensure perps work + cp := currency.NewPair(currency.BTC, currency.NewCode("USDC-PERPETUAL")) + resp, err := d.GetCurrencyTradeURL(context.Background(), asset.Futures, cp) + require.NoError(t, err) + assert.NotEmpty(t, resp) + // specific test to ensure options with dates work + cp = currency.NewPair(currency.BTC, currency.NewCode("14JUN24-62000-C")) + resp, err = d.GetCurrencyTradeURL(context.Background(), asset.Options, cp) + require.NoError(t, err) + assert.NotEmpty(t, resp) +} diff --git a/exchanges/deribit/deribit_websocket.go b/exchanges/deribit/deribit_websocket.go index 44647643e1b..29b4163d36c 100644 --- a/exchanges/deribit/deribit_websocket.go +++ b/exchanges/deribit/deribit_websocket.go @@ -255,7 +255,7 @@ func (d *Deribit) wsHandleData(respRaw []byte) error { accessLog := &wsAccessLog{} return d.processData(respRaw, accessLog) case "changes": - return d.processChanges(respRaw, channels) + return d.processUserOrderChanges(respRaw, channels) case "lock": userLock := &WsUserLock{} return d.processData(respRaw, userLock) @@ -265,7 +265,7 @@ func (d *Deribit) wsHandleData(respRaw []byte) error { } return d.processData(respRaw, data) case "orders": - return d.processOrders(respRaw, channels) + return d.processUserOrders(respRaw, channels) case "portfolio": portfolio := &wsUserPortfolio{} return d.processData(respRaw, portfolio) @@ -294,52 +294,32 @@ func (d *Deribit) wsHandleData(respRaw []byte) error { return nil } -func (d *Deribit) processOrders(respRaw []byte, channels []string) error { - var currencyPair currency.Pair - var err error - var a asset.Item - switch len(channels) { - case 4: - currencyPair, err = currency.NewPairFromString(channels[2]) - if err != nil { - return err - } - case 5: - a, err = d.StringToAssetKind(channels[2]) - if err != nil { - return err - } - default: +func (d *Deribit) processUserOrders(respRaw []byte, channels []string) error { + if len(channels) != 4 && len(channels) != 5 { return fmt.Errorf("%w, expected format 'user.orders.{instrument_name}.raw, user.orders.{instrument_name}.{interval}, user.orders.{kind}.{currency}.raw, or user.orders.{kind}.{currency}.{interval}', but found %s", errMalformedData, strings.Join(channels, ".")) } var response WsResponse orderData := []WsOrder{} response.Params.Data = orderData - err = json.Unmarshal(respRaw, &response) + err := json.Unmarshal(respRaw, &response) if err != nil { return err } orderDetails := make([]order.Detail, len(orderData)) for x := range orderData { - oType, err := order.StringToOrderType(orderData[x].OrderType) + cp, a, err := d.getAssetPairByInstrument(orderData[x].InstrumentName) if err != nil { return err } - side, err := order.StringToOrderSide(orderData[x].Direction) + oType, err := order.StringToOrderType(orderData[x].OrderType) if err != nil { return err } - status, err := order.StringToOrderStatus(orderData[x].OrderState) + side, err := order.StringToOrderSide(orderData[x].Direction) if err != nil { return err } - if a != asset.Empty { - currencyPair, err = currency.NewPairFromString(orderData[x].InstrumentName) - if err != nil { - return err - } - } - a, err = guessAssetTypeFromInstrument(currencyPair) + status, err := order.StringToOrderStatus(orderData[x].OrderState) if err != nil { return err } @@ -356,14 +336,17 @@ func (d *Deribit) processOrders(respRaw []byte, channels []string) error { AssetType: a, Date: orderData[x].CreationTimestamp.Time(), LastUpdated: orderData[x].LastUpdateTimestamp.Time(), - Pair: currencyPair, + Pair: cp, } } d.Websocket.DataHandler <- orderDetails return nil } -func (d *Deribit) processChanges(respRaw []byte, channels []string) error { +func (d *Deribit) processUserOrderChanges(respRaw []byte, channels []string) error { + if len(channels) < 4 || len(channels) > 5 { + return fmt.Errorf("%w, expected format 'trades.{instrument_name}.{interval} or trades.{kind}.{currency}.{interval}', but found %s", errMalformedData, strings.Join(channels, ".")) + } var response WsResponse changeData := &wsChanges{} response.Params.Data = changeData @@ -371,43 +354,22 @@ func (d *Deribit) processChanges(respRaw []byte, channels []string) error { if err != nil { return err } - var currencyPair currency.Pair - var a asset.Item - switch len(channels) { - case 4: - currencyPair, err = currency.NewPairFromString(channels[2]) - if err != nil { - return err - } - case 5: - a, err = d.StringToAssetKind(channels[2]) - if err != nil { - return err - } - default: - return fmt.Errorf("%w, expected format 'trades.{instrument_name}.{interval} or trades.{kind}.{currency}.{interval}', but found %s", errMalformedData, strings.Join(channels, ".")) - } - tradeDatas := make([]trade.Data, len(changeData.Trades)) + td := make([]trade.Data, len(changeData.Trades)) for x := range changeData.Trades { var side order.Side side, err = order.StringToOrderSide(changeData.Trades[x].Direction) if err != nil { return err } - if currencyPair.IsEmpty() { - currencyPair, err = currency.NewPairFromString(changeData.Trades[x].InstrumentName) - if err != nil { - return err - } - } - if a == asset.Empty { - a, err = guessAssetTypeFromInstrument(currencyPair) - if err != nil { - return err - } + var cp currency.Pair + var a asset.Item + cp, a, err = d.getAssetPairByInstrument(changeData.Trades[x].InstrumentName) + if err != nil { + return err } - tradeDatas[x] = trade.Data{ - CurrencyPair: currencyPair, + + td[x] = trade.Data{ + CurrencyPair: cp, Exchange: d.Name, Timestamp: changeData.Trades[x].Timestamp.Time(), Price: changeData.Trades[x].Price, @@ -417,7 +379,7 @@ func (d *Deribit) processChanges(respRaw []byte, channels []string) error { AssetType: a, } } - err = trade.AddTradesToBuffer(d.Name, tradeDatas...) + err = trade.AddTradesToBuffer(d.Name, td...) if err != nil { return err } @@ -435,16 +397,9 @@ func (d *Deribit) processChanges(respRaw []byte, channels []string) error { if err != nil { return err } - if a != asset.Empty { - currencyPair, err = currency.NewPairFromString(changeData.Orders[x].InstrumentName) - if err != nil { - return err - } - } else { - a, err = guessAssetTypeFromInstrument(currencyPair) - if err != nil { - return err - } + cp, a, err := d.getAssetPairByInstrument(changeData.Orders[x].InstrumentName) + if err != nil { + return err } orders[x] = order.Detail{ Price: changeData.Orders[x].Price, @@ -459,7 +414,7 @@ func (d *Deribit) processChanges(respRaw []byte, channels []string) error { AssetType: a, Date: changeData.Orders[x].CreationTimestamp.Time(), LastUpdated: changeData.Orders[x].LastUpdateTimestamp.Time(), - Pair: currencyPair, + Pair: cp, } } d.Websocket.DataHandler <- orders @@ -468,7 +423,7 @@ func (d *Deribit) processChanges(respRaw []byte, channels []string) error { } func (d *Deribit) processQuoteTicker(respRaw []byte, channels []string) error { - cp, err := currency.NewPairFromString(channels[1]) + cp, a, err := d.getAssetPairByInstrument(channels[1]) if err != nil { return err } @@ -479,10 +434,6 @@ func (d *Deribit) processQuoteTicker(respRaw []byte, channels []string) error { if err != nil { return err } - a, err := guessAssetTypeFromInstrument(cp) - if err != nil { - return err - } d.Websocket.DataHandler <- &ticker.Price{ ExchangeName: d.Name, Pair: cp, @@ -497,55 +448,33 @@ func (d *Deribit) processQuoteTicker(respRaw []byte, channels []string) error { } func (d *Deribit) processTrades(respRaw []byte, channels []string) error { - var err error - var currencyPair currency.Pair - var a asset.Item - switch { - case (len(channels) == 3 && channels[0] == "trades") || (len(channels) == 4 && channels[0] == "user"): - currencyPair, err = currency.NewPairFromString(channels[len(channels)-2]) - if err != nil { - return err - } - case (len(channels) == 4 && channels[0] == "trades") || (len(channels) == 5 && channels[0] == "user"): - a, err = d.StringToAssetKind(channels[len(channels)-3]) - if err != nil { - return err - } - default: + if len(channels) < 3 || len(channels) > 5 { return fmt.Errorf("%w, expected format 'trades.{instrument_name}.{interval} or trades.{kind}.{currency}.{interval}', but found %s", errMalformedData, strings.Join(channels, ".")) } var response WsResponse - tradeList := []wsTrade{} + var tradeList []wsTrade response.Params.Data = &tradeList - err = json.Unmarshal(respRaw, &response) + err := json.Unmarshal(respRaw, &response) if err != nil { return err } if len(tradeList) == 0 { return fmt.Errorf("%v, empty list of trades found", common.ErrNoResponse) } - if a == asset.Empty && currencyPair.IsEmpty() { - currencyPair, err = currency.NewPairFromString(tradeList[0].InstrumentName) - if err != nil { - return err - } - a, err = guessAssetTypeFromInstrument(currencyPair) - if err != nil { - return err - } - } tradeDatas := make([]trade.Data, len(tradeList)) for x := range tradeDatas { - side, err := order.StringToOrderSide(tradeList[x].Direction) + var cp currency.Pair + var a asset.Item + cp, a, err = d.getAssetPairByInstrument(tradeList[x].InstrumentName) if err != nil { return err } - currencyPair, err = currency.NewPairFromString(tradeList[x].InstrumentName) + side, err := order.StringToOrderSide(tradeList[x].Direction) if err != nil { return err } tradeDatas[x] = trade.Data{ - CurrencyPair: currencyPair, + CurrencyPair: cp, Exchange: d.Name, Timestamp: tradeList[x].Timestamp.Time(), Price: tradeList[x].Price, @@ -562,7 +491,7 @@ func (d *Deribit) processIncrementalTicker(respRaw []byte, channels []string) er if len(channels) != 2 { return fmt.Errorf("%w, expected format 'incremental_ticker.{instrument_name}', but found %s", errMalformedData, strings.Join(channels, ".")) } - cp, err := currency.NewPairFromString(channels[1]) + cp, a, err := d.getAssetPairByInstrument(channels[1]) if err != nil { return err } @@ -573,14 +502,10 @@ func (d *Deribit) processIncrementalTicker(respRaw []byte, channels []string) er if err != nil { return err } - assetType, err := guessAssetTypeFromInstrument(cp) - if err != nil { - return err - } d.Websocket.DataHandler <- &ticker.Price{ ExchangeName: d.Name, Pair: cp, - AssetType: assetType, + AssetType: a, LastUpdated: incrementalTicker.Timestamp.Time(), BidSize: incrementalTicker.BestBidAmount, AskSize: incrementalTicker.BestAskAmount, @@ -602,11 +527,10 @@ func (d *Deribit) processInstrumentTicker(respRaw []byte, channels []string) err } func (d *Deribit) processTicker(respRaw []byte, channels []string) error { - cp, err := currency.NewPairFromString(channels[1]) + cp, a, err := d.getAssetPairByInstrument(channels[1]) if err != nil { return err } - var a asset.Item var response WsResponse tickerPriceResponse := &wsTicker{} response.Params.Data = tickerPriceResponse @@ -614,10 +538,6 @@ func (d *Deribit) processTicker(respRaw []byte, channels []string) error { if err != nil { return err } - a, err = guessAssetTypeFromInstrument(cp) - if err != nil { - return err - } tickerPrice := &ticker.Price{ ExchangeName: d.Name, Pair: cp, @@ -658,22 +578,17 @@ func (d *Deribit) processCandleChart(respRaw []byte, channels []string) error { if len(channels) != 4 { return fmt.Errorf("%w, expected format 'chart.trades.{instrument_name}.{resolution}', but found %s", errMalformedData, strings.Join(channels, ".")) } - cp, err := currency.NewPairFromString(channels[2]) + cp, a, err := d.getAssetPairByInstrument(channels[2]) if err != nil { return err } var response WsResponse - var a asset.Item candleData := &wsCandlestickData{} response.Params.Data = candleData err = json.Unmarshal(respRaw, &response) if err != nil { return err } - a, err = guessAssetTypeFromInstrument(cp) - if err != nil { - return err - } d.Websocket.DataHandler <- stream.KlineData{ Timestamp: time.UnixMilli(candleData.Tick), Pair: cp, @@ -696,9 +611,8 @@ func (d *Deribit) processOrderbook(respRaw []byte, channels []string) error { if err != nil { return err } - var assetType asset.Item if len(channels) == 3 { - cp, err := currency.NewPairFromString(orderbookData.InstrumentName) + cp, a, err := d.getAssetPairByInstrument(orderbookData.InstrumentName) if err != nil { return err } @@ -743,10 +657,6 @@ func (d *Deribit) processOrderbook(respRaw []byte, channels []string) error { if len(asks) == 0 && len(bids) == 0 { return nil } - assetType, err = guessAssetTypeFromInstrument(cp) - if err != nil { - return err - } if orderbookData.Type == "snapshot" { return d.Websocket.Orderbook.LoadSnapshot(&orderbook.Base{ Exchange: d.Name, @@ -755,7 +665,7 @@ func (d *Deribit) processOrderbook(respRaw []byte, channels []string) error { Pair: cp, Asks: asks, Bids: bids, - Asset: assetType, + Asset: a, LastUpdateID: orderbookData.ChangeID, }) } else if orderbookData.Type == "change" { @@ -763,17 +673,13 @@ func (d *Deribit) processOrderbook(respRaw []byte, channels []string) error { Asks: asks, Bids: bids, Pair: cp, - Asset: assetType, + Asset: a, UpdateID: orderbookData.ChangeID, UpdateTime: orderbookData.Timestamp.Time(), }) } } else if len(channels) == 5 { - cp, err := currency.NewPairFromString(orderbookData.InstrumentName) - if err != nil { - return err - } - assetType, err = guessAssetTypeFromInstrument(cp) + cp, a, err := d.getAssetPairByInstrument(orderbookData.InstrumentName) if err != nil { return err } @@ -824,7 +730,7 @@ func (d *Deribit) processOrderbook(respRaw []byte, channels []string) error { Asks: asks, Bids: bids, Pair: cp, - Asset: assetType, + Asset: a, Exchange: d.Name, LastUpdateID: orderbookData.ChangeID, LastUpdated: orderbookData.Timestamp.Time(), @@ -864,8 +770,8 @@ func (d *Deribit) GenerateDefaultSubscriptions() (subscription.List, error) { case chartTradesChannel: for _, a := range assets { for z := range assetPairs[a] { - if ((assetPairs[a][z].Quote.Upper().String() == "PERPETUAL" || - !strings.Contains(assetPairs[a][z].Quote.Upper().String(), "PERPETUAL")) && + if ((assetPairs[a][z].Quote.Upper().String() == perpString || + !strings.Contains(assetPairs[a][z].Quote.Upper().String(), perpString)) && a == asset.Futures) || (a != asset.Spot && a != asset.Futures) { continue } @@ -885,8 +791,8 @@ func (d *Deribit) GenerateDefaultSubscriptions() (subscription.List, error) { rawUserOrdersChannel: for _, a := range assets { for z := range assetPairs[a] { - if ((assetPairs[a][z].Quote.Upper().String() == "PERPETUAL" || - !strings.Contains(assetPairs[a][z].Quote.Upper().String(), "PERPETUAL")) && + if ((assetPairs[a][z].Quote.Upper().String() == perpString || + !strings.Contains(assetPairs[a][z].Quote.Upper().String(), perpString)) && a == asset.Futures) || (a != asset.Spot && a != asset.Futures) { continue } @@ -900,8 +806,8 @@ func (d *Deribit) GenerateDefaultSubscriptions() (subscription.List, error) { case orderbookChannel: for _, a := range assets { for z := range assetPairs[a] { - if ((assetPairs[a][z].Quote.Upper().String() == "PERPETUAL" || - !strings.Contains(assetPairs[a][z].Quote.Upper().String(), "PERPETUAL")) && + if ((assetPairs[a][z].Quote.Upper().String() == perpString || + !strings.Contains(assetPairs[a][z].Quote.Upper().String(), perpString)) && a == asset.Futures) || (a != asset.Spot && a != asset.Futures) { continue } @@ -936,8 +842,8 @@ func (d *Deribit) GenerateDefaultSubscriptions() (subscription.List, error) { tradesChannel: for _, a := range assets { for z := range assetPairs[a] { - if ((assetPairs[a][z].Quote.Upper().String() != "PERPETUAL" && - !strings.Contains(assetPairs[a][z].Quote.Upper().String(), "PERPETUAL")) && + if ((assetPairs[a][z].Quote.Upper().String() != perpString && + !strings.Contains(assetPairs[a][z].Quote.Upper().String(), perpString)) && a == asset.Futures) || (a != asset.Spot && a != asset.Futures) { continue } @@ -955,7 +861,7 @@ func (d *Deribit) GenerateDefaultSubscriptions() (subscription.List, error) { userTradesChannelByInstrument: for _, a := range assets { for z := range assetPairs[a] { - if subscriptionChannels[x] == perpetualChannel && !strings.Contains(assetPairs[a][z].Quote.Upper().String(), "PERPETUAL") { + if subscriptionChannels[x] == perpetualChannel && !strings.Contains(assetPairs[a][z].Quote.Upper().String(), perpString) { continue } subscriptions = append(subscriptions, diff --git a/exchanges/deribit/deribit_wrapper.go b/exchanges/deribit/deribit_wrapper.go index faa7050a716..742ba92d325 100644 --- a/exchanges/deribit/deribit_wrapper.go +++ b/exchanges/deribit/deribit_wrapper.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "regexp" "sort" "strconv" "strings" @@ -62,16 +63,14 @@ func (d *Deribit) SetDefaults() { d.API.CredentialsValidator.RequiresKey = true d.API.CredentialsValidator.RequiresSecret = true - requestFmt := ¤cy.PairFormat{Uppercase: true, Delimiter: currency.DashDelimiter} - configFmt := ¤cy.PairFormat{Uppercase: true, Delimiter: currency.DashDelimiter} - err := d.StoreAssetPairFormat(asset.Spot, currency.PairStore{ - RequestFormat: ¤cy.PairFormat{Uppercase: true, Delimiter: currency.UnderscoreDelimiter}, - ConfigFormat: ¤cy.PairFormat{Uppercase: true, Delimiter: currency.UnderscoreDelimiter}}) + dashFormat := ¤cy.PairFormat{Uppercase: true, Delimiter: currency.DashDelimiter} + underscoreFormat := ¤cy.PairFormat{Uppercase: true, Delimiter: currency.UnderscoreDelimiter} + err := d.StoreAssetPairFormat(asset.Spot, currency.PairStore{RequestFormat: underscoreFormat, ConfigFormat: underscoreFormat}) if err != nil { log.Errorln(log.ExchangeSys, err) } for _, assetType := range []asset.Item{asset.Futures, asset.Options, asset.OptionCombo, asset.FutureCombo} { - if err = d.StoreAssetPairFormat(assetType, currency.PairStore{RequestFormat: requestFmt, ConfigFormat: configFmt}); err != nil { + if err = d.StoreAssetPairFormat(assetType, currency.PairStore{RequestFormat: dashFormat, ConfigFormat: dashFormat}); err != nil { log.Errorln(log.ExchangeSys, err) } } @@ -208,6 +207,10 @@ func (d *Deribit) Setup(exch *config.Exchange) error { if err != nil { return err } + + // setup option decimal regex at startup to make constant checks more efficient + optionRegex = regexp.MustCompile(optionDecimalRegex) + return d.Websocket.SetupNewConnection(stream.ConnectionSetup{ URL: d.Websocket.GetWebsocketURL(), ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout, @@ -238,16 +241,9 @@ func (d *Deribit) FetchTradablePairs(ctx context.Context, assetType asset.Item) continue } var cp currency.Pair - if assetType == asset.Options { - cp, err = currency.NewPairDelimiter(instrumentsData[y].InstrumentName, currency.DashDelimiter) - if err != nil { - return nil, err - } - } else { - cp, err = currency.NewPairFromString(instrumentsData[y].InstrumentName) - if err != nil { - return nil, err - } + cp, err = currency.NewPairFromString(instrumentsData[y].InstrumentName) + if err != nil { + return nil, err } resp = resp.Add(cp) } @@ -259,17 +255,19 @@ func (d *Deribit) FetchTradablePairs(ctx context.Context, assetType asset.Item) // them in the exchanges config func (d *Deribit) UpdateTradablePairs(ctx context.Context, forceUpdate bool) error { assets := d.GetAssetTypes(false) + errs := common.CollectErrors(len(assets)) for x := range assets { - pairs, err := d.FetchTradablePairs(ctx, assets[x]) - if err != nil { - return err - } - err = d.UpdatePairs(pairs, assets[x], false, forceUpdate) - if err != nil { - return err - } + go func(x int) { + defer errs.Wg.Done() + pairs, err := d.FetchTradablePairs(ctx, assets[x]) + if err != nil { + errs.C <- err + return + } + errs.C <- d.UpdatePairs(pairs, assets[x], false, forceUpdate) + }(x) } - return nil + return errs.Collect() } // UpdateTickers updates the ticker for all currency pairs of a given asset type @@ -1091,7 +1089,7 @@ func (d *Deribit) GetHistoricCandles(ctx context.Context, pair currency.Pair, a if err != nil { return nil, err } - intervalString, err := d.GetResolutionFromInterval(interval) + intervalString, err := d.GetResolutionFromInterval(req.ExchangeInterval) if err != nil { return nil, err } @@ -1149,7 +1147,7 @@ func (d *Deribit) GetHistoricCandlesExtended(ctx context.Context, pair currency. switch a { case asset.Futures, asset.Spot: for x := range req.RangeHolder.Ranges { - intervalString, err := d.GetResolutionFromInterval(interval) + intervalString, err := d.GetResolutionFromInterval(req.ExchangeInterval) if err != nil { return nil, err } @@ -1266,59 +1264,6 @@ func (d *Deribit) GetFuturesContractDetails(ctx context.Context, item asset.Item return resp, nil } -// GetLatestFundingRates returns the latest funding rates data -func (d *Deribit) GetLatestFundingRates(ctx context.Context, r *fundingrate.LatestRateRequest) ([]fundingrate.LatestRateResponse, error) { - if r == nil { - return nil, fmt.Errorf("%w LatestRateRequest", common.ErrNilPointer) - } - if !d.SupportsAsset(r.Asset) { - return nil, fmt.Errorf("%s %w", r.Asset, asset.ErrNotSupported) - } - isPerpetual, err := d.IsPerpetualFutureCurrency(r.Asset, r.Pair) - if !isPerpetual || err != nil { - return nil, futures.ErrNotPerpetualFuture - } - available, err := d.GetAvailablePairs(r.Asset) - if err != nil { - return nil, err - } - if !available.Contains(r.Pair, true) && r.Pair.Quote.String() != "PERPETUAL" && !strings.HasSuffix(r.Pair.String(), "PERP") { - return nil, fmt.Errorf("%w pair: %v", futures.ErrNotPerpetualFuture, r.Pair) - } - r.Pair, err = d.FormatExchangeCurrency(r.Pair, r.Asset) - if err != nil { - return nil, err - } - var fri []FundingRateHistory - fri, err = d.GetFundingRateHistory(ctx, r.Pair.String(), time.Now().Add(-time.Hour*16), time.Now()) - if err != nil { - return nil, err - } - - resp := make([]fundingrate.LatestRateResponse, 1) - latestTime := fri[0].Timestamp.Time() - for i := range fri { - if fri[i].Timestamp.Time().Before(latestTime) { - continue - } - resp[0] = fundingrate.LatestRateResponse{ - TimeChecked: time.Now(), - Exchange: d.Name, - Asset: r.Asset, - Pair: r.Pair, - LatestRate: fundingrate.Rate{ - Time: fri[i].Timestamp.Time(), - Rate: decimal.NewFromFloat(fri[i].Interest8H), - }, - } - latestTime = fri[i].Timestamp.Time() - } - if len(resp) == 0 { - return nil, fmt.Errorf("%w %v %v", futures.ErrNotPerpetualFuture, r.Asset, r.Pair) - } - return resp, nil -} - // UpdateOrderExecutionLimits sets exchange execution order limits for an asset type func (d *Deribit) UpdateOrderExecutionLimits(ctx context.Context, a asset.Item) error { if !d.SupportsAsset(a) { @@ -1449,26 +1394,23 @@ func (d *Deribit) GetOpenInterest(ctx context.Context, k ...key.PairAsset) ([]fu } } result := make([]futures.OpenInterest, 0, len(k)) - var err error - var pair currency.Pair for i := range k { - pair, err = d.FormatExchangeCurrency(k[i].Pair(), k[i].Asset) + pFmt, err := d.CurrencyPairs.GetFormat(k[i].Asset, true) if err != nil { return nil, err } + cp := k[i].Pair().Format(pFmt) + p := d.formatPairString(k[i].Asset, cp) var oi []BookSummaryData if d.Websocket.IsConnected() { - oi, err = d.WSRetrieveBookBySummary(pair.Base, d.GetAssetKind(k[i].Asset)) + oi, err = d.WSRetrieveBookSummaryByInstrument(p) } else { - oi, err = d.GetBookSummaryByCurrency(ctx, pair.Base, d.GetAssetKind(k[i].Asset)) + oi, err = d.GetBookSummaryByInstrument(ctx, p) } if err != nil { return nil, err } for a := range oi { - if oi[a].InstrumentName != pair.String() { - continue - } result = append(result, futures.OpenInterest{ Key: key.ExchangePairAsset{ Exchange: d.Name, @@ -1487,28 +1429,105 @@ func (d *Deribit) GetOpenInterest(ctx context.Context, k ...key.PairAsset) ([]fu return result, nil } +// GetCurrencyTradeURL returns the URL to the exchange's trade page for the given asset and currency pair +func (d *Deribit) GetCurrencyTradeURL(_ context.Context, a asset.Item, cp currency.Pair) (string, error) { + if cp.IsEmpty() { + return "", currency.ErrCurrencyPairEmpty + } + switch a { + case asset.Futures: + isPerp, err := d.IsPerpetualFutureCurrency(a, cp) + if err != nil { + return "", err + } + if isPerp { + return tradeBaseURL + tradeFutures + cp.Base.Upper().String() + currency.UnderscoreDelimiter + cp.Quote.Upper().String(), nil + } + return tradeBaseURL + tradeFutures + cp.Upper().String(), nil + case asset.Spot: + cp.Delimiter = currency.UnderscoreDelimiter + return tradeBaseURL + tradeSpot + cp.Upper().String(), nil + case asset.Options: + baseString := cp.Base.Upper().String() + quoteString := cp.Quote.Upper().String() + quoteSplit := strings.Split(quoteString, currency.DashDelimiter) + if len(quoteSplit) > 1 && + (quoteSplit[len(quoteSplit)-1] == "C" || quoteSplit[len(quoteSplit)-1] == "P") { + return tradeBaseURL + tradeOptions + baseString + "/" + baseString + currency.DashDelimiter + quoteSplit[0], nil + } + return tradeBaseURL + tradeOptions + baseString, nil + case asset.FutureCombo: + return tradeBaseURL + tradeFuturesCombo + cp.Upper().String(), nil + case asset.OptionCombo: + return tradeBaseURL + tradeOptionsCombo + cp.Base.Upper().String(), nil + default: + return "", fmt.Errorf("%w %v", asset.ErrNotSupported, a) + } +} + // IsPerpetualFutureCurrency ensures a given asset and currency is a perpetual future // differs by exchange func (d *Deribit) IsPerpetualFutureCurrency(assetType asset.Item, pair currency.Pair) (bool, error) { - if !assetType.IsFutures() { - return false, futures.ErrNotPerpetualFuture - } else if strings.EqualFold(pair.Quote.String(), "PERPETUAL") || strings.HasSuffix(pair.String(), "PERP") { - return true, nil + if pair.IsEmpty() { + return false, currency.ErrCurrencyPairEmpty + } + if assetType != asset.Futures { + // deribit considers future combo, even if ending in "PERP" to not be a perpetual + return false, nil } - pair, err := d.FormatExchangeCurrency(pair, assetType) + pqs := strings.Split(pair.Quote.Upper().String(), currency.DashDelimiter) + return pqs[len(pqs)-1] == perpString, nil +} + +// GetLatestFundingRates returns the latest funding rates data +func (d *Deribit) GetLatestFundingRates(ctx context.Context, r *fundingrate.LatestRateRequest) ([]fundingrate.LatestRateResponse, error) { + if r == nil { + return nil, fmt.Errorf("%w LatestRateRequest", common.ErrNilPointer) + } + if !d.SupportsAsset(r.Asset) { + return nil, fmt.Errorf("%s %w", r.Asset, asset.ErrNotSupported) + } + isPerpetual, err := d.IsPerpetualFutureCurrency(r.Asset, r.Pair) if err != nil { - return false, err + return nil, err } - var instrumentInfo *InstrumentData - if d.Websocket.IsConnected() { - instrumentInfo, err = d.WSRetrieveInstrumentData(pair.String()) - } else { - instrumentInfo, err = d.GetInstrument(context.Background(), pair.String()) + if !isPerpetual { + return nil, fmt.Errorf("%w '%s'", futures.ErrNotPerpetualFuture, r.Pair) + } + pFmt, err := d.CurrencyPairs.GetFormat(r.Asset, true) + if err != nil { + return nil, err } + cp := r.Pair.Format(pFmt) + p := d.formatPairString(r.Asset, cp) + var fri []FundingRateHistory + fri, err = d.GetFundingRateHistory(ctx, p, time.Now().Add(-time.Hour*16), time.Now()) if err != nil { - return false, err + return nil, err + } + + resp := make([]fundingrate.LatestRateResponse, 1) + latestTime := fri[0].Timestamp.Time() + for i := range fri { + if fri[i].Timestamp.Time().Before(latestTime) { + continue + } + resp[0] = fundingrate.LatestRateResponse{ + TimeChecked: time.Now(), + Exchange: d.Name, + Asset: r.Asset, + Pair: r.Pair, + LatestRate: fundingrate.Rate{ + Time: fri[i].Timestamp.Time(), + Rate: decimal.NewFromFloat(fri[i].Interest8H), + }, + } + latestTime = fri[i].Timestamp.Time() } - return strings.EqualFold(instrumentInfo.SettlementPeriod, "perpetual"), nil + if len(resp) == 0 { + return nil, fmt.Errorf("%w %v %v", futures.ErrNotPerpetualFuture, r.Asset, r.Pair) + } + return resp, nil } // GetHistoricalFundingRates returns historical funding rates for a future @@ -1532,10 +1551,12 @@ func (d *Deribit) GetHistoricalFundingRates(ctx context.Context, r *fundingrate. if r.IncludePayments { return nil, fmt.Errorf("include payments %w", common.ErrNotYetImplemented) } - fPair, err := d.FormatExchangeCurrency(r.Pair, r.Asset) + pFmt, err := d.CurrencyPairs.GetFormat(r.Asset, true) if err != nil { return nil, err } + cp := r.Pair.Format(pFmt) + p := d.formatPairString(r.Asset, cp) ed := r.EndDate var fundingRates []fundingrate.Rate @@ -1546,9 +1567,9 @@ func (d *Deribit) GetHistoricalFundingRates(ctx context.Context, r *fundingrate. } var records []FundingRateHistory if d.Websocket.IsConnected() { - records, err = d.WSRetrieveFundingRateHistory(fPair.String(), r.StartDate, ed) + records, err = d.WSRetrieveFundingRateHistory(p, r.StartDate, ed) } else { - records, err = d.GetFundingRateHistory(ctx, fPair.String(), r.StartDate, ed) + records, err = d.GetFundingRateHistory(ctx, p, r.StartDate, ed) } if err != nil { return nil, err diff --git a/exchanges/kline/kline.go b/exchanges/kline/kline.go index 2360b9bb5f9..5a8ea21ebb1 100644 --- a/exchanges/kline/kline.go +++ b/exchanges/kline/kline.go @@ -198,8 +198,9 @@ func (k *Item) addPadding(start, exclusiveEnd time.Time, purgeOnPartial bool) er padded[x].Time = start case !k.Candles[target].Time.Equal(start): if k.Candles[target].Time.Before(start) { - return fmt.Errorf("%w when it should be %s truncated at a %s interval", + return fmt.Errorf("%w '%s' should be '%s' at '%s' interval", errCandleOpenTimeIsNotUTCAligned, + k.Candles[target].Time, start.Add(k.Interval.Duration()), k.Interval) } diff --git a/exchanges/orderbook/orderbook.go b/exchanges/orderbook/orderbook.go index bd6afbceb67..60514d6cb53 100644 --- a/exchanges/orderbook/orderbook.go +++ b/exchanges/orderbook/orderbook.go @@ -216,7 +216,7 @@ func (b *Base) Verify() error { // level books. In the event that there is a massive liquidity change where // a book dries up, this will still update so we do not traverse potential // incorrect old data. - if len(b.Asks) == 0 || len(b.Bids) == 0 { + if (len(b.Asks) == 0 || len(b.Bids) == 0) && !b.Asset.IsOptions() { log.Warnf(log.OrderBook, bookLengthIssue, b.Exchange, From eacf14cffa79b3ba41fbfd12ce170fcaa4306029 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Jun 2024 17:06:01 +1000 Subject: [PATCH 6/7] build(deps): Bump golang.org/x/net from 0.25.0 to 0.26.0 (#1562) Bumps [golang.org/x/net](https://github.com/golang/net) from 0.25.0 to 0.26.0. - [Commits](https://github.com/golang/net/compare/v0.25.0...v0.26.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 502f9ed384f..ab19da4e40f 100644 --- a/go.mod +++ b/go.mod @@ -23,9 +23,9 @@ require ( github.com/thrasher-corp/sqlboiler v1.0.1-0.20191001234224-71e17f37a85e github.com/urfave/cli/v2 v2.27.2 github.com/volatiletech/null v8.0.0+incompatible - golang.org/x/crypto v0.23.0 - golang.org/x/net v0.25.0 - golang.org/x/text v0.15.0 + golang.org/x/crypto v0.24.0 + golang.org/x/net v0.26.0 + golang.org/x/text v0.16.0 golang.org/x/time v0.5.0 google.golang.org/genproto/googleapis/api v0.0.0-20240513163218-0867130af1f8 google.golang.org/grpc v1.64.0 @@ -57,7 +57,7 @@ require ( go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect - golang.org/x/sys v0.20.0 // indirect + golang.org/x/sys v0.21.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8 // indirect gopkg.in/ini.v1 v1.67.0 // indirect diff --git a/go.sum b/go.sum index 0a9f5450ae0..adb13833011 100644 --- a/go.sum +++ b/go.sum @@ -251,8 +251,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= -golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= +golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= @@ -273,8 +273,8 @@ golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= -golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo= golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= @@ -295,13 +295,13 @@ golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190927073244-c990c680b611/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= -golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= From 06b9980f77764fe799bf40a07e07db450128d93e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Jun 2024 17:32:51 +1000 Subject: [PATCH 7/7] build(deps): Bump github.com/gorilla/websocket from 1.5.1 to 1.5.2 (#1563) Bumps [github.com/gorilla/websocket](https://github.com/gorilla/websocket) from 1.5.1 to 1.5.2. - [Release notes](https://github.com/gorilla/websocket/releases) - [Commits](https://github.com/gorilla/websocket/compare/v1.5.1...v1.5.2) --- updated-dependencies: - dependency-name: github.com/gorilla/websocket dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index ab19da4e40f..11f9ccf8e73 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/d5/tengo/v2 v2.17.0 github.com/gofrs/uuid v4.4.0+incompatible github.com/gorilla/mux v1.8.1 - github.com/gorilla/websocket v1.5.1 + github.com/gorilla/websocket v1.5.2 github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 github.com/kat-co/vala v0.0.0-20170210184112-42e1d8b61f12 diff --git a/go.sum b/go.sum index adb13833011..9834713b1b6 100644 --- a/go.sum +++ b/go.sum @@ -87,8 +87,8 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= -github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= +github.com/gorilla/websocket v1.5.2 h1:qoW6V1GT3aZxybsbC6oLnailWnB+qTMVwMreOso9XUw= +github.com/gorilla/websocket v1.5.2/go.mod h1:0n9H61RBAcf5/38py2MCYbxzPIY9rOkpvvMT24Rqs30= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8=