From 8639f7fd214f2d6649925ed7dcf661d5393424d6 Mon Sep 17 00:00:00 2001 From: Elad Gildnur <6321801+shleikes@users.noreply.github.com> Date: Wed, 9 Oct 2024 11:24:36 +0300 Subject: [PATCH 1/6] Add provider address to log when failed subscription (#1732) --- protocol/rpcconsumer/rpcconsumer_server.go | 1 + 1 file changed, 1 insertion(+) diff --git a/protocol/rpcconsumer/rpcconsumer_server.go b/protocol/rpcconsumer/rpcconsumer_server.go index af11a2d952..698cb5b68c 100644 --- a/protocol/rpcconsumer/rpcconsumer_server.go +++ b/protocol/rpcconsumer/rpcconsumer_server.go @@ -689,6 +689,7 @@ func (rpccs *RPCConsumerServer) sendRelayToProvider( utils.LavaFormatError("Failed relaySubscriptionInner", errResponse, utils.LogAttr("Request", localRelayRequestData), utils.LogAttr("Request data", string(localRelayRequestData.Data)), + utils.LogAttr("Provider", providerPublicAddress), ) } From bf46091bd8070daf33a0978d14b95f034956ff9f Mon Sep 17 00:00:00 2001 From: Elad Gildnur <6321801+shleikes@users.noreply.github.com> Date: Wed, 9 Oct 2024 11:27:03 +0300 Subject: [PATCH 2/6] feat: PRT - Result generic parser (#1711) * Add generic parsers to parse directives * Separate raw block from parsed block to support result parsing * Small fix to verification * Prettier code * Remove buggy line * Unquote if needed * fixing bug in failing to parse output * add check for length 0 * add check for length * rename to make sense * CR Fix: Add tests for ParseRawBlock * CR Fix: Rename * CR Fix: Remove unused function * CR Fix: Add comment * CR Fix: Reuse GetResult return value * CR Fix: Use NOT_APPLICABLE as default * CR FIx: Add comment * CR Fix: boolean rename * CR Fix: Default value * CR Fix: change returned success behavior --------- Co-authored-by: Ran Mishael Co-authored-by: Omer <100387053+omerlavanet@users.noreply.github.com> --- proto/lavanet/lava/spec/api_collection.proto | 1 + protocol/chainlib/chain_fetcher.go | 112 ++++---- protocol/chainlib/grpc_test.go | 10 +- protocol/chainlib/jsonRPC_test.go | 5 +- protocol/chainlib/rest_test.go | 10 +- protocol/parser/parser.go | 220 ++++++++------- protocol/parser/parser_test.go | 229 ++++++++++++++- x/spec/types/api_collection.pb.go | 276 ++++++++++++------- 8 files changed, 587 insertions(+), 276 deletions(-) diff --git a/proto/lavanet/lava/spec/api_collection.proto b/proto/lavanet/lava/spec/api_collection.proto index 4f9c7434e1..27750346b6 100644 --- a/proto/lavanet/lava/spec/api_collection.proto +++ b/proto/lavanet/lava/spec/api_collection.proto @@ -83,6 +83,7 @@ message ParseDirective { string function_template = 2; BlockParser result_parsing = 3 [(gogoproto.nullable) = false]; string api_name = 4; + repeated GenericParser parsers = 5 [(gogoproto.nullable) = false]; } message BlockParser { diff --git a/protocol/chainlib/chain_fetcher.go b/protocol/chainlib/chain_fetcher.go index 565ace6948..9a5d6c421b 100644 --- a/protocol/chainlib/chain_fetcher.go +++ b/protocol/chainlib/chain_fetcher.go @@ -188,68 +188,71 @@ func (cf *ChainFetcher) Verify(ctx context.Context, verification VerificationCon ) } - parsedResult, err := parser.ParseFromReply(parserInput, parsing.ResultParsing) - if err != nil { - return utils.LavaFormatWarning("[-] verify failed to parse result", err, []utils.Attribute{ - {Key: "chainId", Value: chainId}, - {Key: "nodeUrl", Value: proxyUrl.Url}, - {Key: "Method", Value: parsing.GetApiName()}, - {Key: "Response", Value: string(reply.RelayReply.Data)}, - }...) + parsedInput := parser.ParseBlockFromReply(parserInput, parsing.ResultParsing, parsing.Parsers) + if parsedInput.GetRawParsedData() == "" { + return utils.LavaFormatWarning("[-] verify failed to parse result", err, + utils.LogAttr("chainId", chainId), + utils.LogAttr("nodeUrl", proxyUrl.Url), + utils.LogAttr("Method", parsing.GetApiName()), + utils.LogAttr("Response", string(reply.RelayReply.Data)), + ) } if verification.LatestDistance != 0 && latestBlock != 0 && verification.ParseDirective.FunctionTag != spectypes.FUNCTION_TAG_GET_BLOCK_BY_NUM { - parsedResultAsNumber, err := strconv.ParseUint(parsedResult, 0, 64) - if err != nil { - return utils.LavaFormatWarning("[-] verify failed to parse result as number", err, []utils.Attribute{ - {Key: "chainId", Value: chainId}, - {Key: "nodeUrl", Value: proxyUrl.Url}, - {Key: "Method", Value: parsing.GetApiName()}, - {Key: "Response", Value: string(reply.RelayReply.Data)}, - {Key: "parsedResult", Value: parsedResult}, - }...) + parsedResultAsNumber := parsedInput.GetBlock() + if parsedResultAsNumber == spectypes.NOT_APPLICABLE { + return utils.LavaFormatWarning("[-] verify failed to parse result as number", err, + utils.LogAttr("chainId", chainId), + utils.LogAttr("nodeUrl", proxyUrl.Url), + utils.LogAttr("Method", parsing.GetApiName()), + utils.LogAttr("Response", string(reply.RelayReply.Data)), + utils.LogAttr("rawParsedData", parsedInput.GetRawParsedData()), + ) } - if parsedResultAsNumber > latestBlock { - return utils.LavaFormatWarning("[-] verify failed parsed result is greater than latestBlock", err, []utils.Attribute{ - {Key: "chainId", Value: chainId}, - {Key: "nodeUrl", Value: proxyUrl.Url}, - {Key: "Method", Value: parsing.GetApiName()}, - {Key: "latestBlock", Value: latestBlock}, - {Key: "parsedResult", Value: parsedResultAsNumber}, - }...) + uint64ParsedResultAsNumber := uint64(parsedResultAsNumber) + if uint64ParsedResultAsNumber > latestBlock { + return utils.LavaFormatWarning("[-] verify failed parsed result is greater than latestBlock", err, + utils.LogAttr("chainId", chainId), + utils.LogAttr("nodeUrl", proxyUrl.Url), + utils.LogAttr("Method", parsing.GetApiName()), + utils.LogAttr("latestBlock", latestBlock), + utils.LogAttr("parsedResult", uint64ParsedResultAsNumber), + ) } - if latestBlock-parsedResultAsNumber < verification.LatestDistance { - return utils.LavaFormatWarning("[-] verify failed expected block distance is not sufficient", err, []utils.Attribute{ - {Key: "chainId", Value: chainId}, - {Key: "nodeUrl", Value: proxyUrl.Url}, - {Key: "Method", Value: parsing.GetApiName()}, - {Key: "latestBlock", Value: latestBlock}, - {Key: "parsedResult", Value: parsedResultAsNumber}, - {Key: "expected", Value: verification.LatestDistance}, - }...) + if latestBlock-uint64ParsedResultAsNumber < verification.LatestDistance { + return utils.LavaFormatWarning("[-] verify failed expected block distance is not sufficient", err, + utils.LogAttr("chainId", chainId), + utils.LogAttr("nodeUrl", proxyUrl.Url), + utils.LogAttr("Method", parsing.GetApiName()), + utils.LogAttr("latestBlock", latestBlock), + utils.LogAttr("parsedResult", uint64ParsedResultAsNumber), + utils.LogAttr("expected", verification.LatestDistance), + ) } } // some verifications only want the response to be valid, and don't care about the value if verification.Value != "*" && verification.Value != "" && verification.ParseDirective.FunctionTag != spectypes.FUNCTION_TAG_GET_BLOCK_BY_NUM { - if parsedResult != verification.Value { - return utils.LavaFormatWarning("[-] verify failed expected and received are different", err, []utils.Attribute{ - {Key: "chainId", Value: chainId}, - {Key: "nodeUrl", Value: proxyUrl.Url}, - {Key: "parsedResult", Value: parsedResult}, - {Key: "verification.Value", Value: verification.Value}, - {Key: "Method", Value: parsing.GetApiName()}, - {Key: "Extension", Value: verification.Extension}, - {Key: "Addon", Value: verification.Addon}, - {Key: "Verification", Value: verification.Name}, - }...) + rawData := parsedInput.GetRawParsedData() + if rawData != verification.Value { + return utils.LavaFormatWarning("[-] verify failed expected and received are different", err, + utils.LogAttr("chainId", chainId), + utils.LogAttr("nodeUrl", proxyUrl.Url), + utils.LogAttr("rawParsedBlock", rawData), + utils.LogAttr("verification.Value", verification.Value), + utils.LogAttr("Method", parsing.GetApiName()), + utils.LogAttr("Extension", verification.Extension), + utils.LogAttr("Addon", verification.Addon), + utils.LogAttr("Verification", verification.Name), + ) } } utils.LavaFormatInfo("[+] verified successfully", - utils.Attribute{Key: "chainId", Value: chainId}, - utils.Attribute{Key: "nodeUrl", Value: proxyUrl.Url}, - utils.Attribute{Key: "verification", Value: verification.Name}, - utils.Attribute{Key: "value", Value: parser.CapStringLen(parsedResult)}, - utils.Attribute{Key: "verificationKey", Value: verification.VerificationKey}, - utils.Attribute{Key: "apiInterface", Value: cf.endpoint.ApiInterface}, + utils.LogAttr("chainId", chainId), + utils.LogAttr("nodeUrl", proxyUrl.Url), + utils.LogAttr("verification", verification.Name), + utils.LogAttr("block", parsedInput.GetBlock()), + utils.LogAttr("rawData", parsedInput.GetRawParsedData()), + utils.LogAttr("verificationKey", verification.VerificationKey), + utils.LogAttr("apiInterface", cf.endpoint.ApiInterface), ) return nil } @@ -292,8 +295,9 @@ func (cf *ChainFetcher) FetchLatestBlockNum(ctx context.Context) (int64, error) {Key: "error", Value: err}, }...) } - blockNum, err := parser.ParseBlockFromReply(parserInput, parsing.ResultParsing) - if err != nil { + parsedInput := parser.ParseBlockFromReply(parserInput, parsing.ResultParsing, parsing.Parsers) + blockNum := parsedInput.GetBlock() + if blockNum == spectypes.NOT_APPLICABLE { return spectypes.NOT_APPLICABLE, utils.LavaFormatDebug(tagName+" Failed to parse Response", []utils.Attribute{ {Key: "chainId", Value: chainId}, {Key: "nodeUrl", Value: proxyUrl.Url}, @@ -355,7 +359,7 @@ func (cf *ChainFetcher) FetchBlockHashByNum(ctx context.Context, blockNum int64) }...) } - res, err := parser.ParseFromReplyAndDecode(parserInput, parsing.ResultParsing) + res, err := parser.ParseBlockHashFromReplyAndDecode(parserInput, parsing.ResultParsing, parsing.Parsers) if err != nil { return "", utils.LavaFormatDebug(tagName+" Failed ParseMessageResponse", []utils.Attribute{ {Key: "error", Value: err}, diff --git a/protocol/chainlib/grpc_test.go b/protocol/chainlib/grpc_test.go index 8c51ab9ca1..68e5d2ece5 100644 --- a/protocol/chainlib/grpc_test.go +++ b/protocol/chainlib/grpc_test.go @@ -217,9 +217,8 @@ func TestParsingRequestedBlocksHeadersGrpc(t *testing.T) { require.NoError(t, err) parserInput, err := FormatResponseForParsing(reply.RelayReply, chainMessage) require.NoError(t, err) - blockNum, err := parser.ParseBlockFromReply(parserInput, parsingForCrafting.ResultParsing) - require.NoError(t, err) - require.Equal(t, test.block, blockNum) + parsedInput := parser.ParseBlockFromReply(parserInput, parsingForCrafting.ResultParsing, nil) + require.Equal(t, test.block, parsedInput.GetBlock()) }) } } @@ -287,9 +286,8 @@ func TestSettingBlocksHeadersGrpc(t *testing.T) { require.NoError(t, err) parserInput, err := FormatResponseForParsing(reply.RelayReply, chainMessage) require.NoError(t, err) - blockNum, err := parser.ParseBlockFromReply(parserInput, parsingForCrafting.ResultParsing) - require.NoError(t, err) - require.Equal(t, test.block, blockNum) + parsedInput := parser.ParseBlockFromReply(parserInput, parsingForCrafting.ResultParsing, nil) + require.Equal(t, test.block, parsedInput.GetBlock()) }) } } diff --git a/protocol/chainlib/jsonRPC_test.go b/protocol/chainlib/jsonRPC_test.go index f32ec51683..7158e98704 100644 --- a/protocol/chainlib/jsonRPC_test.go +++ b/protocol/chainlib/jsonRPC_test.go @@ -187,8 +187,9 @@ func TestJsonRpcChainProxy(t *testing.T) { require.NoError(t, err) _, err = chainFetcher.FetchBlockHashByNum(ctx, block) - errMsg := "GET_BLOCK_BY_NUM Failed ParseMessageResponse {error:invalid parser input format" - require.True(t, err.Error()[:len(errMsg)] == errMsg, err.Error()) + actualErrMsg := "GET_BLOCK_BY_NUM Failed ParseMessageResponse {error:blockParsing - parse failed {error:invalid parser input format," + expectedErrMsg := err.Error()[:len(actualErrMsg)] + require.Equal(t, actualErrMsg, expectedErrMsg, err.Error()) } func TestAddonAndVerifications(t *testing.T) { diff --git a/protocol/chainlib/rest_test.go b/protocol/chainlib/rest_test.go index 6ce0922113..918888bf1a 100644 --- a/protocol/chainlib/rest_test.go +++ b/protocol/chainlib/rest_test.go @@ -214,9 +214,8 @@ func TestParsingRequestedBlocksHeadersRest(t *testing.T) { require.NoError(t, err) parserInput, err := FormatResponseForParsing(reply.RelayReply, chainMessage) require.NoError(t, err) - blockNum, err := parser.ParseBlockFromReply(parserInput, parsingForCrafting.ResultParsing) - require.NoError(t, err) - require.Equal(t, test.block, blockNum) + parsedInput := parser.ParseBlockFromReply(parserInput, parsingForCrafting.ResultParsing, nil) + require.Equal(t, test.block, parsedInput.GetBlock()) }) } } @@ -289,9 +288,8 @@ func TestSettingRequestedBlocksHeadersRest(t *testing.T) { require.NoError(t, err) parserInput, err := FormatResponseForParsing(reply.RelayReply, chainMessage) require.NoError(t, err) - blockNum, err := parser.ParseBlockFromReply(parserInput, parsingForCrafting.ResultParsing) - require.NoError(t, err) - require.Equal(t, test.block, blockNum) + parsedInput := parser.ParseBlockFromReply(parserInput, parsingForCrafting.ResultParsing, nil) + require.Equal(t, test.block, parsedInput.GetBlock()) }) } } diff --git a/protocol/parser/parser.go b/protocol/parser/parser.go index 19cff573f5..0eb958704f 100644 --- a/protocol/parser/parser.go +++ b/protocol/parser/parser.go @@ -50,6 +50,7 @@ func ParseDefaultBlockParameter(block string) (int64, error) { // try to parse a number } + block = unquoteString(block) hashNoPrefix, found := strings.CutPrefix(block, "0x") if len(block) >= 64 && found { if len(hashNoPrefix)%64 == 0 { @@ -96,131 +97,161 @@ func filterGenericParsersByType(genericParsers []spectypes.GenericParser, filter return retGenericParsers } -func parseInputFromParamsWithGenericParsers(rpcInput RPCInput, genericParsers []spectypes.GenericParser) (*ParsedInput, bool) { - parsedSuccessfully := false +func parseInputWithGenericParsers(rpcInput RPCInput, genericParsers []spectypes.GenericParser) (*ParsedInput, bool) { + managedToParseRawBlock := false if len(genericParsers) == 0 { - return nil, parsedSuccessfully + return nil, managedToParseRawBlock } genericParserResult, genericParserErr := ParseWithGenericParsers(rpcInput, filterGenericParsersByType(genericParsers, getParserTypeMap(PARSE_PARAMS))) if genericParserErr != nil { - return nil, parsedSuccessfully + return nil, managedToParseRawBlock } parsed := NewParsedInput() - parsedBlock := genericParserResult.GetBlock() - if parsedBlock != spectypes.NOT_APPLICABLE { - parsedSuccessfully = true - parsed.parsedBlock = parsedBlock + rawParsedData := genericParserResult.GetRawParsedData() + if rawParsedData != "" { + managedToParseRawBlock = true + parsed.parsedDataRaw = rawParsedData } parsedBlockHashes, err := genericParserResult.GetBlockHashes() if err == nil { + managedToParseRawBlock = true parsed.parsedHashes = parsedBlockHashes } - return parsed, parsedSuccessfully + return parsed, managedToParseRawBlock } -func ParseBlockFromParams(rpcInput RPCInput, blockParser spectypes.BlockParser, genericParsers []spectypes.GenericParser) *ParsedInput { - parsedBlockInfo, parsedSuccessfully := parseInputFromParamsWithGenericParsers(rpcInput, genericParsers) - if parsedSuccessfully { - return parsedBlockInfo - } - if parsedBlockInfo == nil { - parsedBlockInfo = NewParsedInput() - } - - parsedBlockInfo.parsedBlock = func() int64 { - // first we try to parse the value with the block parser - result, err := parse(rpcInput, blockParser, PARSE_PARAMS) - if err != nil || result == nil { - utils.LavaFormatDebug("ParseBlockFromParams - parse failed", - utils.LogAttr("error", err), - utils.LogAttr("result", result), - utils.LogAttr("blockParser", blockParser), - utils.LogAttr("rpcInput", rpcInput), +// ParseRawBlock attempts to parse a block from rpcInput and store it in parsedInput. +// If parsing fails or rawBlock is empty, it uses defaultValue if provided. +// If parsing the defaultValue also fails, it sets the block to NOT_APPLICABLE. +func ParseRawBlock(rpcInput RPCInput, parsedInput *ParsedInput, defaultValue string) { + rawBlock := parsedInput.GetRawParsedData() + var parsedBlock int64 + var err error + if rawBlock != "" { + parsedBlock, err = rpcInput.ParseBlock(rawBlock) + } + if rawBlock == "" || err != nil { + if defaultValue != "" { + utils.LavaFormatDebug("Failed parsing block from string, assuming default value", + utils.LogAttr("params", rpcInput.GetParams()), + utils.LogAttr("failed_parsed_value", rawBlock), + utils.LogAttr("default_value", defaultValue), ) - return spectypes.NOT_APPLICABLE - } - - resString, ok := result[0].(string) - if !ok { - utils.LavaFormatDebug("ParseBlockFromParams - result[0].(string) - type assertion failed", utils.LogAttr("result[0]", result[0])) - return spectypes.NOT_APPLICABLE - } - parsedBlock, err := rpcInput.ParseBlock(resString) - if err != nil { - if blockParser.DefaultValue != "" { - utils.LavaFormatDebug("Failed parsing block from string, assuming default value", - utils.LogAttr("params", rpcInput.GetParams()), - utils.LogAttr("failed_parsed_value", resString), - utils.LogAttr("default_value", blockParser.DefaultValue), + parsedBlock, err = rpcInput.ParseBlock(defaultValue) + if err != nil { + utils.LavaFormatError("Failed parsing default value, setting to NOT_APPLICABLE", err, + utils.LogAttr("default_value", defaultValue), ) - parsedBlock, err = rpcInput.ParseBlock(blockParser.DefaultValue) - if err != nil { - utils.LavaFormatError("Failed parsing default value, setting to NOT_APPLICABLE", err, - utils.LogAttr("default_value", blockParser.DefaultValue), - ) - return spectypes.NOT_APPLICABLE - } - } else { - return spectypes.NOT_APPLICABLE + parsedBlock = spectypes.NOT_APPLICABLE } + } else { + parsedBlock = spectypes.NOT_APPLICABLE } - return parsedBlock - }() - - return parsedBlockInfo + } + parsedInput.SetBlock(parsedBlock) } -// This returns the parsed response without decoding -func ParseFromReply(rpcInput RPCInput, blockParser spectypes.BlockParser) (string, error) { - result, err := parse(rpcInput, blockParser, PARSE_RESULT) +func parseInputWithLegacyBlockParser(rpcInput RPCInput, blockParser spectypes.BlockParser, source int) (string, error) { + result, err := legacyParse(rpcInput, blockParser, source) if err != nil || result == nil { - utils.LavaFormatDebug("ParseBlockFromParams - parse failed", + return "", utils.LavaFormatDebug("blockParsing - parse failed", utils.LogAttr("error", err), utils.LogAttr("result", result), utils.LogAttr("blockParser", blockParser), utils.LogAttr("rpcInput", rpcInput), ) - return "", err } - response, ok := result[spectypes.DEFAULT_PARSED_RESULT_INDEX].(string) + resString, ok := result[spectypes.DEFAULT_PARSED_RESULT_INDEX].(string) if !ok { - return "", utils.LavaFormatError("Failed to Convert blockData[spectypes.DEFAULT_PARSED_RESULT_INDEX].(string)", nil, utils.Attribute{Key: "blockData", Value: response[spectypes.DEFAULT_PARSED_RESULT_INDEX]}) + return "", utils.LavaFormatDebug("blockParsing - result[0].(string) - type assertion failed", utils.LogAttr("result[0]", result[0])) } - if strings.Contains(response, "\"") { - responseUnquoted, err := strconv.Unquote(response) - if err != nil { - return response, nil + return resString, nil +} + +// parseBlock processes the given RPC input using either generic parsers or a legacy block parser. +// It first attempts to parse the input with the provided generic parsers. If successful, it returns +// the parsed information after unquoting the raw parsed data. If the generic parsing fails, it falls +// back to using a legacy block parser. +// +// Parameters: +// - rpcInput: The input data to be parsed. +// - blockParser: The legacy block parser to use if generic parsing fails. +// - genericParsers: A slice of generic parsers to attempt first. +// - source: An integer representing the source of the input: either PARSE_PARAMS or PARSE_RESULT. +// +// Returns: +// - A pointer to a ParsedInput struct containing the parsed data. +func parseBlock(rpcInput RPCInput, blockParser spectypes.BlockParser, genericParsers []spectypes.GenericParser, source int) *ParsedInput { + parsedBlockInfo, _ := parseInputWithGenericParsers(rpcInput, genericParsers) + if parsedBlockInfo == nil { + parsedBlockInfo = NewParsedInput() + } else { + rawBlockFromGenericParser := parsedBlockInfo.parsedDataRaw + if rawBlockFromGenericParser != "" { + parsedBlockInfo.parsedDataRaw = unquoteString(rawBlockFromGenericParser) + return parsedBlockInfo } - return responseUnquoted, nil } - return response, nil + parsedRawBlock, _ := parseInputWithLegacyBlockParser(rpcInput, blockParser, source) + parsedBlockInfo.parsedDataRaw = unquoteString(parsedRawBlock) + return parsedBlockInfo +} + +func ParseBlockFromParams(rpcInput RPCInput, blockParser spectypes.BlockParser, genericParsers []spectypes.GenericParser) *ParsedInput { + parsedInput := parseBlock(rpcInput, blockParser, genericParsers, PARSE_PARAMS) + ParseRawBlock(rpcInput, parsedInput, blockParser.DefaultValue) + return parsedInput } -func ParseBlockFromReply(rpcInput RPCInput, blockParser spectypes.BlockParser) (int64, error) { - result, err := ParseFromReply(rpcInput, blockParser) +func ParseBlockFromReply(rpcInput RPCInput, blockParser spectypes.BlockParser, genericParsers []spectypes.GenericParser) *ParsedInput { + parsedInput := parseBlock(rpcInput, blockParser, genericParsers, PARSE_RESULT) + ParseRawBlock(rpcInput, parsedInput, blockParser.DefaultValue) + return parsedInput +} + +func unquoteString(str string) string { + if !strings.Contains(str, "\"") { + return str + } + + unquoted, err := strconv.Unquote(str) if err != nil { - return spectypes.NOT_APPLICABLE, err + return str } - return rpcInput.ParseBlock(result) + return unquoted } // This returns the parsed response after decoding -func ParseFromReplyAndDecode(rpcInput RPCInput, resultParser spectypes.BlockParser) (string, error) { - response, err := ParseFromReply(rpcInput, resultParser) +func ParseBlockHashFromReplyAndDecode(rpcInput RPCInput, resultParser spectypes.BlockParser, genericParsers []spectypes.GenericParser) (string, error) { + parsedInput, _ := parseInputWithGenericParsers(rpcInput, genericParsers) + if parsedInput == nil { + parsedBlockHashFromBlockParser, err := parseInputWithLegacyBlockParser(rpcInput, resultParser, PARSE_RESULT) + if err != nil { + return "", err + } + return parseResponseByEncoding([]byte(parsedBlockHashFromBlockParser), resultParser.Encoding) + } + + parsedBlockHashes, err := parsedInput.GetBlockHashes() if err != nil { return "", err } - return parseResponseByEncoding([]byte(response), resultParser.Encoding) + + numberOfParsedHashes := len(parsedBlockHashes) + if numberOfParsedHashes != 1 { + return "", utils.LavaFormatError("[ParseBlockHashFromReplyAndDecode] expected parsed hashes length 1", nil, utils.LogAttr("rpcInput.GetResult()", rpcInput.GetResult()), utils.LogAttr("hashes_length", numberOfParsedHashes)) + } + return parseResponseByEncoding([]byte(parsedBlockHashes[0]), resultParser.Encoding) } -func parse(rpcInput RPCInput, blockParser spectypes.BlockParser, dataSource int) ([]interface{}, error) { +func legacyParse(rpcInput RPCInput, blockParser spectypes.BlockParser, dataSource int) ([]interface{}, error) { var retval []interface{} var err error @@ -254,14 +285,18 @@ func parse(rpcInput RPCInput, blockParser spectypes.BlockParser, dataSource int) } type ParsedInput struct { - parsedBlock int64 - parsedHashes []string + parsedDataRaw string + parsedBlock int64 + parsedHashes []string } +const RAW_NOT_APPLICABLE = "-1" + func NewParsedInput() *ParsedInput { return &ParsedInput{ - parsedBlock: spectypes.NOT_APPLICABLE, - parsedHashes: make([]string, 0), + parsedDataRaw: RAW_NOT_APPLICABLE, + parsedBlock: spectypes.NOT_APPLICABLE, + parsedHashes: make([]string, 0), } } @@ -269,6 +304,10 @@ func (p *ParsedInput) SetBlock(block int64) { p.parsedBlock = block } +func (p *ParsedInput) GetRawParsedData() string { + return p.parsedDataRaw +} + func (p *ParsedInput) GetBlock() int64 { return p.parsedBlock } @@ -281,7 +320,12 @@ func (p *ParsedInput) GetBlockHashes() ([]string, error) { } func getMapForParse(rpcInput RPCInput) map[string]interface{} { - return map[string]interface{}{"params": rpcInput.GetParams(), "result": rpcInput.GetResult()} + var result map[string]interface{} + rpcInputResult := rpcInput.GetResult() + if rpcInputResult != nil { + json.Unmarshal(rpcInputResult, &result) + } + return map[string]interface{}{"params": rpcInput.GetParams(), "result": result} } func ParseWithGenericParsers(rpcInput RPCInput, genericParsers []spectypes.GenericParser) (*ParsedInput, error) { @@ -364,22 +408,14 @@ func parseGeneric(input interface{}, genericParser spectypes.GenericParser) (*Pa // regardless of the value provided by the user. for example .finality: final case spectypes.PARSER_TYPE_DEFAULT_VALUE: parsed := NewParsedInput() - block, err := ParseDefaultBlockParameter(genericParser.Value) - if err != nil { - return nil, utils.LavaFormatError("Failed converting default value to requested block", err, utils.LogAttr("genericParser.Value", genericParser.Value)) - } - parsed.parsedBlock = block + parsed.parsedDataRaw = genericParser.Value return parsed, nil // Case Block Latest, setting the value set by the user given a json path hit. // Example: block_id: 100, will result in requested block 100. case spectypes.PARSER_TYPE_BLOCK_LATEST: parsed := NewParsedInput() - valueString := blockInterfaceToString(value) - block, err := ParseDefaultBlockParameter(valueString) - if err != nil { - return nil, utils.LavaFormatWarning("Failed converting valueString to block number", err, utils.LogAttr("value", valueString)) - } - parsed.parsedBlock = block + block := blockInterfaceToString(value) + parsed.parsedDataRaw = block return parsed, nil case spectypes.PARSER_TYPE_BLOCK_HASH: return parseGenericParserBlockHash(value) diff --git a/protocol/parser/parser_test.go b/protocol/parser/parser_test.go index b90e2cd226..c005804850 100644 --- a/protocol/parser/parser_test.go +++ b/protocol/parser/parser_test.go @@ -346,8 +346,8 @@ func TestParseBlockFromParamsHappyFlow(t *testing.T) { for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { - block := ParseBlockFromParams(&testCase.message, testCase.blockParser, nil) - require.Equal(t, testCase.expectedBlock, block.parsedBlock) + parsedInput := ParseBlockFromParams(&testCase.message, testCase.blockParser, nil) + require.Equal(t, testCase.expectedBlock, parsedInput.GetBlock()) }) } } @@ -405,9 +405,8 @@ func TestParseBlockFromReplyHappyFlow(t *testing.T) { for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { - block, err := ParseBlockFromReply(&testCase.message, testCase.blockParser) - require.NoError(t, err, fmt.Sprintf("Test case name: %s", testCase.name)) - require.Equal(t, testCase.expectedBlock, block) + parsedInput := ParseBlockFromReply(&testCase.message, testCase.blockParser, nil) + require.Equal(t, testCase.expectedBlock, parsedInput.GetBlock()) }) } } @@ -585,7 +584,143 @@ func TestParseBlockFromParams(t *testing.T) { t.Run(test.name, func(t *testing.T) { t.Parallel() result := ParseBlockFromParams(test.rpcInput, test.blockParser, test.genericParsers) - require.Equal(t, test.expected, result.parsedBlock) + require.Equal(t, test.expected, result.GetBlock()) + }) + } +} + +func TestParseBlockFromReply(t *testing.T) { + tests := []struct { + name string + rpcInput RPCInput + blockParser spectypes.BlockParser + genericParsers []spectypes.GenericParser + expected int64 + }{ + { + name: "generic_parser_happy_flow_default_value", + rpcInput: &RPCInputTest{ + Result: []byte(` + { + "foo": { + "bar": [ + { + "baz": 123 + } + ] + } + } + `), + }, + genericParsers: []spectypes.GenericParser{ + { + ParsePath: ".result.foo.bar.[0].baz", + Rule: "=123", + Value: "latest", + ParseType: spectypes.PARSER_TYPE_DEFAULT_VALUE, + }, + }, + expected: spectypes.LATEST_BLOCK, + }, + { + name: "generic_parser_happy_flow_value", + rpcInput: &RPCInputTest{ + Result: []byte(` + { + "foo": { + "bar": [ + { + "baz": 123 + } + ] + } + } + `), + }, + genericParsers: []spectypes.GenericParser{ + { + ParsePath: ".result.foo.bar.[0].baz", + ParseType: spectypes.PARSER_TYPE_BLOCK_LATEST, + }, + }, + expected: 123, + }, + { + name: "generic_parser_nil_params", + rpcInput: &RPCInputTest{}, + genericParsers: []spectypes.GenericParser{ + { + ParsePath: ".result.foo", + Value: "latest", + ParseType: spectypes.PARSER_TYPE_DEFAULT_VALUE, + }, + }, + expected: spectypes.NOT_APPLICABLE, + }, + { + name: "generic_parser_fail_with_nil_var", + rpcInput: &RPCInputTest{ + Result: []byte(` + { + "bar": 123 + } + `), + }, + genericParsers: []spectypes.GenericParser{ + { + ParsePath: ".result.foo", + Value: "latest", + ParseType: spectypes.PARSER_TYPE_DEFAULT_VALUE, + }, + }, + expected: spectypes.NOT_APPLICABLE, + }, + { + name: "generic_parser_fail_with_iter_error", + rpcInput: &RPCInputTest{ + Result: []byte(` + { + "bar": 123 + } + `), + }, + genericParsers: []spectypes.GenericParser{ + { + ParsePath: ".result.bar.foo", + Value: "latest", + ParseType: spectypes.PARSER_TYPE_DEFAULT_VALUE, + }, + }, + expected: spectypes.NOT_APPLICABLE, + }, + { + name: "generic_parser_wrong_jq_path_with_parser_func_default", + rpcInput: &RPCInputTest{ + Params: map[string]interface{}{ + "bar": 123, + }, + }, + genericParsers: []spectypes.GenericParser{ + { + ParsePath: "!@#$%^&*()", + Value: "latest", + ParseType: spectypes.PARSER_TYPE_DEFAULT_VALUE, + }, + }, + blockParser: spectypes.BlockParser{ + ParserFunc: spectypes.PARSER_FUNC_DEFAULT, + ParserArg: []string{"latest"}, + }, + expected: spectypes.LATEST_BLOCK, + }, + } + + for _, test := range tests { + test := test + t.Run(test.name, func(t *testing.T) { + t.Parallel() + parsedInput := ParseBlockFromReply(test.rpcInput, test.blockParser, test.genericParsers) + require.Equal(t, test.expected, parsedInput.GetBlock()) }) } } @@ -642,14 +777,17 @@ func TestParseBlockFromParamsHash(t *testing.T) { for _, test := range tests { test := test t.Run(test.name, func(t *testing.T) { - t.Parallel() - result := ParseBlockFromParams(test.rpcInput, test.blockParser, test.genericParsers) + // t.Parallel() + parsedInput := ParseBlockFromParams(test.rpcInput, test.blockParser, test.genericParsers) + parsedHashes, err := parsedInput.GetBlockHashes() if test.expectedHash == "" { - require.Len(t, result.parsedHashes, 0) + require.Error(t, err) + require.Len(t, parsedHashes, 0) } else { - require.Equal(t, test.expectedHash, result.parsedHashes[0]) + require.NoError(t, err) + require.Equal(t, test.expectedHash, parsedHashes[0]) } - require.Equal(t, test.expected, result.parsedBlock) + require.Equal(t, test.expected, parsedInput.GetBlock()) }) } } @@ -686,14 +824,14 @@ func TestParseGeneric(t *testing.T) { "finality": "final", }, } - res, err := parseGeneric(jsonMap, spectypes.GenericParser{ + parsedInput, err := parseGeneric(jsonMap, spectypes.GenericParser{ ParsePath: ".params.finality", Value: "latest", ParseType: spectypes.PARSER_TYPE_DEFAULT_VALUE, Rule: "=final || =optimistic", }) require.NoError(t, err) - require.Equal(t, spectypes.LATEST_BLOCK, res.parsedBlock) + require.Equal(t, "latest", parsedInput.GetRawParsedData()) } func TestHashLengthValidation(t *testing.T) { @@ -702,3 +840,68 @@ func TestHashLengthValidation(t *testing.T) { _, err = parseGenericParserBlockHash("123456789,123456789,123456789,12") require.NoError(t, err) } + +func TestParseRawBlock(t *testing.T) { + defaultValue := "defaultValue" + defaultBlock := int64(1) + rawBlock := "123" + expectedBlock := int64(123) + + t.Run("with raw block parsed", func(t *testing.T) { + parsedInput := &ParsedInput{ + parsedDataRaw: rawBlock, + } + + rpcInput := RPCInputTest{ + ParseBlockFunc: func(block string) (int64, error) { + require.Equal(t, parsedInput.parsedDataRaw, block) + return expectedBlock, nil + }, + } + + ParseRawBlock(&rpcInput, parsedInput, defaultValue) + require.Equal(t, expectedBlock, parsedInput.GetBlock()) + }) + + t.Run("without raw block parsed, with default value", func(t *testing.T) { + parsedInput := &ParsedInput{} + + rpcInput := RPCInputTest{ + ParseBlockFunc: func(block string) (int64, error) { + require.Equal(t, defaultValue, block) + return defaultBlock, nil + }, + } + + ParseRawBlock(&rpcInput, parsedInput, defaultValue) + require.Equal(t, defaultBlock, parsedInput.GetBlock()) + }) + + t.Run("without raw block parsed, with default value parse error", func(t *testing.T) { + parsedInput := &ParsedInput{} + + rpcInput := RPCInputTest{ + ParseBlockFunc: func(block string) (int64, error) { + require.Equal(t, defaultValue, block) + return 0, fmt.Errorf("parse error") + }, + } + + ParseRawBlock(&rpcInput, parsedInput, defaultValue) + require.Equal(t, spectypes.NOT_APPLICABLE, parsedInput.GetBlock()) + }) + + t.Run("without raw block parsed, without default value", func(t *testing.T) { + parsedInput := &ParsedInput{} + + rpcInput := RPCInputTest{ + ParseBlockFunc: func(block string) (int64, error) { + require.Fail(t, "should not be called") + return 0, nil + }, + } + + ParseRawBlock(&rpcInput, parsedInput, "") + require.Equal(t, spectypes.NOT_APPLICABLE, parsedInput.GetBlock()) + }) +} diff --git a/x/spec/types/api_collection.pb.go b/x/spec/types/api_collection.pb.go index 5591ce0b28..2cb76295ff 100644 --- a/x/spec/types/api_collection.pb.go +++ b/x/spec/types/api_collection.pb.go @@ -795,10 +795,11 @@ func (m *Api) GetParsers() []GenericParser { } type ParseDirective struct { - FunctionTag FUNCTION_TAG `protobuf:"varint,1,opt,name=function_tag,json=functionTag,proto3,enum=lavanet.lava.spec.FUNCTION_TAG" json:"function_tag,omitempty"` - FunctionTemplate string `protobuf:"bytes,2,opt,name=function_template,json=functionTemplate,proto3" json:"function_template,omitempty"` - ResultParsing BlockParser `protobuf:"bytes,3,opt,name=result_parsing,json=resultParsing,proto3" json:"result_parsing"` - ApiName string `protobuf:"bytes,4,opt,name=api_name,json=apiName,proto3" json:"api_name,omitempty"` + FunctionTag FUNCTION_TAG `protobuf:"varint,1,opt,name=function_tag,json=functionTag,proto3,enum=lavanet.lava.spec.FUNCTION_TAG" json:"function_tag,omitempty"` + FunctionTemplate string `protobuf:"bytes,2,opt,name=function_template,json=functionTemplate,proto3" json:"function_template,omitempty"` + ResultParsing BlockParser `protobuf:"bytes,3,opt,name=result_parsing,json=resultParsing,proto3" json:"result_parsing"` + ApiName string `protobuf:"bytes,4,opt,name=api_name,json=apiName,proto3" json:"api_name,omitempty"` + Parsers []GenericParser `protobuf:"bytes,5,rep,name=parsers,proto3" json:"parsers"` } func (m *ParseDirective) Reset() { *m = ParseDirective{} } @@ -862,6 +863,13 @@ func (m *ParseDirective) GetApiName() string { return "" } +func (m *ParseDirective) GetParsers() []GenericParser { + if m != nil { + return m.Parsers + } + return nil +} + type BlockParser struct { ParserArg []string `protobuf:"bytes,1,rep,name=parser_arg,json=parserArg,proto3" json:"parser_arg,omitempty"` ParserFunc PARSER_FUNC `protobuf:"varint,2,opt,name=parser_func,json=parserFunc,proto3,enum=lavanet.lava.spec.PARSER_FUNC" json:"parser_func,omitempty"` @@ -1100,108 +1108,108 @@ func init() { } var fileDescriptor_c9f7567a181f534f = []byte{ - // 1606 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x57, 0xcf, 0x6f, 0xdb, 0xc8, + // 1613 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x57, 0xcf, 0x6f, 0xdb, 0xc8, 0x15, 0x36, 0x25, 0xea, 0xd7, 0xb3, 0x24, 0x4f, 0x26, 0x6e, 0xaa, 0x4d, 0xb3, 0x96, 0xcb, 0xdd, 0x76, 0x03, 0x2f, 0x6a, 0xa3, 0x0e, 0x0a, 0x14, 0x8b, 0x16, 0x2d, 0x25, 0xd1, 0x89, 0x12, 0x59, 0x32, 0x46, 0xb4, 0x5b, 0xf7, 0x42, 0x8c, 0xa9, 0xb1, 0x3c, 0x58, 0x8a, 0x24, 0xc8, 0xa1, 0x1b, - 0x9f, 0xfb, 0x0f, 0xf4, 0x50, 0xa0, 0xff, 0x42, 0x81, 0x02, 0x05, 0xfa, 0x5f, 0xec, 0x71, 0x6f, - 0xed, 0xc9, 0x28, 0x92, 0x43, 0xd1, 0x1c, 0x73, 0x29, 0x7a, 0x28, 0x50, 0xcc, 0x90, 0xfa, 0xc1, - 0x44, 0x71, 0x91, 0x93, 0x34, 0xdf, 0x7c, 0xf3, 0xcd, 0x9b, 0xf7, 0xde, 0x7c, 0x23, 0xc1, 0x0f, - 0x3d, 0x7a, 0x4d, 0x7d, 0x26, 0x0e, 0xe4, 0xe7, 0x41, 0x1c, 0x32, 0xf7, 0x80, 0x86, 0xdc, 0x71, - 0x03, 0xcf, 0x63, 0xae, 0xe0, 0x81, 0xbf, 0x1f, 0x46, 0x81, 0x08, 0xf0, 0xbd, 0x8c, 0xb7, 0x2f, - 0x3f, 0xf7, 0x25, 0xef, 0xe1, 0xf6, 0x34, 0x98, 0x06, 0x6a, 0xf6, 0x40, 0x7e, 0x4b, 0x89, 0xc6, - 0x7f, 0x8b, 0xd0, 0x30, 0x43, 0xde, 0x5d, 0x08, 0xe0, 0x16, 0x54, 0x98, 0x4f, 0x2f, 0x3c, 0x36, - 0x69, 0x69, 0xbb, 0xda, 0xe3, 0x2a, 0x99, 0x0f, 0xf1, 0x09, 0x6c, 0x2d, 0x37, 0x72, 0x26, 0x54, - 0xd0, 0x56, 0x61, 0x57, 0x7b, 0xbc, 0x79, 0xf8, 0xfd, 0xfd, 0xf7, 0xb6, 0xdb, 0x5f, 0x2a, 0xf6, - 0xa8, 0xa0, 0x1d, 0xfd, 0x9b, 0xdb, 0xf6, 0x06, 0x69, 0xba, 0x39, 0x14, 0xef, 0x81, 0x4e, 0x43, - 0x1e, 0xb7, 0x8a, 0xbb, 0xc5, 0xc7, 0x9b, 0x87, 0x0f, 0xd6, 0xc8, 0x98, 0x21, 0x27, 0x8a, 0x83, - 0x9f, 0x40, 0xe5, 0x8a, 0xd1, 0x09, 0x8b, 0xe2, 0x96, 0xae, 0xe8, 0x9f, 0xac, 0xa1, 0x3f, 0x53, - 0x0c, 0x32, 0x67, 0xe2, 0x01, 0x20, 0xee, 0x5f, 0xb1, 0x88, 0x0b, 0xea, 0xbb, 0xcc, 0x51, 0x9b, - 0x95, 0xd4, 0xea, 0xff, 0x1f, 0x33, 0xd9, 0x5a, 0x59, 0x6a, 0xca, 0x10, 0x06, 0x80, 0x42, 0x1a, - 0xc5, 0xcc, 0x99, 0xf0, 0x48, 0xf2, 0xae, 0x59, 0xdc, 0x2a, 0x7f, 0x50, 0xed, 0x44, 0x52, 0x7b, - 0x73, 0x26, 0xd9, 0x0a, 0x73, 0xe3, 0x18, 0xff, 0x0c, 0x80, 0xbd, 0x14, 0xcc, 0x8f, 0x79, 0xe0, - 0xc7, 0xad, 0x8a, 0xd2, 0x79, 0xb4, 0x46, 0xc7, 0x9a, 0x93, 0xc8, 0x0a, 0x1f, 0x5b, 0xd0, 0xb8, - 0x66, 0x11, 0xbf, 0xe4, 0x2e, 0x15, 0x4a, 0xa0, 0xaa, 0x04, 0xda, 0x6b, 0x04, 0xce, 0x56, 0x78, - 0x24, 0xbf, 0xca, 0xf8, 0x2d, 0xd4, 0x16, 0xfa, 0x18, 0x83, 0xee, 0xd3, 0x19, 0x53, 0x75, 0xaf, - 0x11, 0xf5, 0x1d, 0x7f, 0x09, 0x7a, 0x94, 0x78, 0xac, 0x55, 0x54, 0x95, 0xfe, 0xee, 0x1a, 0x79, - 0x92, 0x78, 0x8c, 0x28, 0x12, 0xfe, 0x0c, 0x1a, 0x6e, 0xe2, 0xcc, 0x12, 0x4f, 0xf0, 0xd0, 0xe3, - 0x2c, 0x6a, 0xe9, 0xbb, 0xda, 0x63, 0x9d, 0xd4, 0xdd, 0xe4, 0x78, 0x81, 0x3d, 0xd7, 0xab, 0x05, - 0x54, 0x34, 0x1e, 0x81, 0x2e, 0x17, 0xe2, 0x6d, 0x28, 0x5d, 0x78, 0x81, 0xfb, 0xb5, 0xda, 0x54, - 0x27, 0xe9, 0xc0, 0xf8, 0xb3, 0x06, 0xf5, 0xd5, 0xb0, 0xd7, 0x86, 0xf6, 0x1c, 0xb6, 0xde, 0x29, - 0xc7, 0x1d, 0xfd, 0xf8, 0x4e, 0x35, 0x9a, 0xf9, 0x6a, 0xe0, 0x9f, 0x40, 0xf9, 0x9a, 0x7a, 0x09, - 0x9b, 0xf7, 0xe2, 0xa7, 0x1f, 0x92, 0x38, 0x93, 0x2c, 0x92, 0x91, 0x9f, 0xeb, 0x55, 0x1d, 0x95, - 0x8c, 0xff, 0x68, 0x00, 0xcb, 0x49, 0xfc, 0x08, 0x6a, 0x8b, 0x42, 0x65, 0x01, 0x2f, 0x01, 0xfc, - 0x03, 0x68, 0xb2, 0x97, 0x21, 0x73, 0x05, 0x9b, 0x38, 0x4a, 0x45, 0x05, 0x5d, 0x23, 0x8d, 0x39, - 0x9a, 0x8a, 0x7c, 0x01, 0x5b, 0x1e, 0x15, 0x2c, 0x16, 0xce, 0x84, 0xc7, 0xaa, 0x05, 0x55, 0x09, - 0x74, 0xd2, 0x4c, 0xe1, 0x5e, 0x86, 0xe2, 0x21, 0x54, 0x63, 0x26, 0x8b, 0x2a, 0x6e, 0x54, 0xba, - 0x9b, 0x87, 0x87, 0x77, 0xc6, 0x9e, 0x6b, 0x87, 0x71, 0xb6, 0x92, 0x2c, 0x34, 0x8c, 0x1f, 0xc1, - 0xf6, 0x3a, 0x06, 0xae, 0x82, 0x7e, 0x44, 0xb9, 0x87, 0x36, 0xf0, 0x26, 0x54, 0x7e, 0x45, 0x23, - 0x9f, 0xfb, 0x53, 0xa4, 0x19, 0x7f, 0x2d, 0x40, 0x33, 0x7f, 0x6f, 0xf0, 0x19, 0x34, 0xa4, 0x29, - 0x71, 0x5f, 0xb0, 0xe8, 0x92, 0xba, 0x59, 0xd1, 0x3a, 0x3f, 0x7e, 0x73, 0xdb, 0xce, 0x4f, 0xbc, - 0xbd, 0x6d, 0x3f, 0x9a, 0xd1, 0x30, 0x16, 0x51, 0xe2, 0x8a, 0x24, 0x62, 0x5f, 0x19, 0xb9, 0x69, - 0x83, 0xd4, 0x69, 0xc8, 0xfb, 0xf3, 0xa1, 0xd4, 0x55, 0x73, 0x3e, 0xf5, 0x9c, 0x90, 0x8a, 0xab, - 0x34, 0x71, 0xa9, 0x6e, 0x6e, 0xe2, 0x7d, 0xdd, 0xdc, 0xb4, 0x41, 0xea, 0xf3, 0xf1, 0x09, 0x15, - 0x57, 0xf8, 0x09, 0xe8, 0xe2, 0x26, 0x4c, 0xf3, 0x5b, 0xeb, 0xb4, 0xdf, 0xdc, 0xb6, 0xd5, 0xf8, - 0xed, 0x6d, 0xfb, 0x7e, 0x5e, 0x45, 0xa2, 0x06, 0x51, 0x93, 0xf8, 0x2b, 0x28, 0xd3, 0xc9, 0xc4, - 0x09, 0x7c, 0x95, 0xf4, 0x5a, 0xe7, 0xb3, 0x37, 0xb7, 0xed, 0x0c, 0x79, 0x7b, 0xdb, 0xfe, 0xce, - 0x3b, 0xc7, 0x52, 0xb8, 0x41, 0x4a, 0x74, 0x32, 0x19, 0xf9, 0xc6, 0x3f, 0x35, 0x28, 0xa7, 0x4e, - 0xb5, 0xb6, 0xaf, 0x7f, 0x0a, 0xfa, 0xd7, 0xdc, 0x9f, 0xa8, 0xe3, 0x35, 0x0f, 0x3f, 0xff, 0xa0, - 0xcd, 0x65, 0x1f, 0xf6, 0x4d, 0xc8, 0x88, 0x5a, 0x81, 0x3b, 0x50, 0xbf, 0x4c, 0xfc, 0xd4, 0x9f, - 0x05, 0x9d, 0xaa, 0x13, 0x35, 0xd7, 0x7a, 0xc2, 0xd1, 0xe9, 0xb0, 0x6b, 0xf7, 0x47, 0x43, 0xc7, - 0x36, 0x9f, 0x92, 0xcd, 0xf9, 0x22, 0x9b, 0x4e, 0x8d, 0x17, 0x00, 0x4b, 0x5d, 0xdc, 0x80, 0x5a, - 0x48, 0xe3, 0xd8, 0x89, 0x99, 0x3f, 0x41, 0x1b, 0xb8, 0x09, 0xa0, 0x86, 0x11, 0x0b, 0xbd, 0x1b, - 0xa4, 0x2d, 0xa6, 0x2f, 0x02, 0x71, 0x85, 0x0a, 0x78, 0x0b, 0x36, 0xd5, 0x90, 0x4f, 0xfd, 0x20, - 0x62, 0xa8, 0x68, 0xfc, 0xbb, 0x00, 0x45, 0x33, 0xe4, 0x77, 0x3c, 0x2a, 0xf3, 0x04, 0x14, 0x56, - 0x12, 0x20, 0x6d, 0x24, 0x98, 0x85, 0x89, 0x60, 0x4e, 0xe2, 0x73, 0x11, 0x67, 0x9d, 0x5f, 0xcf, - 0xc0, 0x53, 0x89, 0xe1, 0x7d, 0xb8, 0xcf, 0x5e, 0x8a, 0x88, 0x3a, 0x79, 0x6a, 0xea, 0x38, 0xf7, - 0xd4, 0x54, 0x77, 0x95, 0x6f, 0x42, 0xd5, 0xa5, 0x82, 0x4d, 0x83, 0xe8, 0xa6, 0x55, 0x56, 0x36, - 0xb1, 0x2e, 0x2f, 0xe3, 0x90, 0xb9, 0xdd, 0x8c, 0x96, 0x3d, 0x5a, 0x8b, 0x65, 0xb8, 0x0f, 0x0d, - 0x65, 0x4f, 0x8e, 0x34, 0x0f, 0xee, 0x4f, 0x5b, 0x15, 0xa5, 0xb3, 0xb3, 0x46, 0xa7, 0x23, 0x79, - 0xea, 0xd2, 0x45, 0x99, 0x4c, 0xfd, 0x62, 0x0e, 0x71, 0x7f, 0x8a, 0x3f, 0x05, 0x10, 0x7c, 0xc6, - 0x82, 0x44, 0x38, 0x33, 0xe9, 0xdd, 0x32, 0xe8, 0x5a, 0x86, 0x1c, 0xc7, 0xf8, 0x97, 0x50, 0x51, - 0x06, 0x15, 0xc5, 0xad, 0x9a, 0xf2, 0xa3, 0xdd, 0x35, 0x7b, 0x3c, 0x65, 0x3e, 0x8b, 0xb8, 0x9b, - 0xdb, 0x65, 0xbe, 0xcc, 0xf8, 0x97, 0x06, 0xcd, 0xbc, 0xe7, 0xbd, 0xd7, 0x1d, 0xda, 0xc7, 0x77, - 0x07, 0xfe, 0x12, 0xee, 0x2d, 0x35, 0xd8, 0x2c, 0x94, 0x66, 0x94, 0xd5, 0x0e, 0x2d, 0x78, 0x19, - 0x8e, 0x5f, 0x40, 0x33, 0x62, 0x71, 0xe2, 0x89, 0x45, 0xc2, 0x8a, 0x1f, 0x91, 0xb0, 0x46, 0xba, - 0x76, 0x9e, 0xb1, 0x4f, 0xa0, 0x2a, 0xdd, 0x41, 0x35, 0x8b, 0xba, 0x72, 0xa4, 0x42, 0x43, 0x3e, - 0xa4, 0x33, 0x66, 0xfc, 0x45, 0x83, 0xcd, 0x95, 0xf5, 0x32, 0xb9, 0x69, 0x1a, 0x1c, 0x1a, 0xc9, - 0x63, 0x16, 0xa5, 0x03, 0xa7, 0x88, 0x19, 0x4d, 0xf1, 0x2f, 0x64, 0x97, 0xaa, 0x69, 0x19, 0x71, - 0x76, 0xcd, 0xd6, 0xc5, 0x74, 0x62, 0x92, 0xb1, 0x45, 0x1c, 0x99, 0x0d, 0x92, 0x29, 0x1e, 0x25, - 0xbe, 0x2b, 0xfb, 0x73, 0xc2, 0x2e, 0xa9, 0x3c, 0x58, 0xea, 0xe0, 0xca, 0x39, 0x48, 0x3d, 0x03, - 0x53, 0x03, 0x7f, 0x08, 0x55, 0xe6, 0xbb, 0xc1, 0x44, 0x1e, 0x3b, 0x8d, 0x77, 0x31, 0x36, 0xfe, - 0xa8, 0x41, 0x23, 0x57, 0xbd, 0x45, 0xc8, 0xa9, 0xb1, 0x65, 0x8f, 0x86, 0x42, 0x94, 0x45, 0x6d, - 0x43, 0x69, 0xf5, 0xad, 0x48, 0x07, 0xf8, 0xe7, 0xf3, 0x45, 0x0b, 0xfb, 0xba, 0xf3, 0x1c, 0xf6, - 0xf9, 0x89, 0x95, 0x89, 0xaa, 0xbb, 0x8d, 0xb3, 0xa7, 0x3d, 0x8d, 0x4e, 0x7d, 0x57, 0x0f, 0xef, - 0xea, 0x1d, 0xc0, 0x9f, 0xcb, 0xb3, 0x0a, 0x16, 0xcd, 0xb8, 0xcf, 0x63, 0xc1, 0xdd, 0xec, 0xfe, - 0xe6, 0x41, 0x19, 0x9f, 0x17, 0xb8, 0xd4, 0x53, 0xf1, 0x55, 0x49, 0x3a, 0xc0, 0x06, 0xd4, 0xe3, - 0xe4, 0x22, 0x76, 0x23, 0x1e, 0xca, 0xbe, 0x50, 0x11, 0x56, 0x49, 0x0e, 0x93, 0x69, 0x8a, 0x05, - 0x15, 0xec, 0x32, 0xf1, 0x54, 0x20, 0x0d, 0xb2, 0x18, 0xe3, 0x36, 0x6c, 0x5e, 0x51, 0x7f, 0xca, - 0xfd, 0xa9, 0xfc, 0xe5, 0xd6, 0x2a, 0xa9, 0xe5, 0x90, 0x41, 0x66, 0xc8, 0xf7, 0x0c, 0xa8, 0x59, - 0xbf, 0xb6, 0xad, 0xe1, 0xb8, 0x3f, 0x1a, 0xca, 0x07, 0x6a, 0x38, 0x1a, 0x5a, 0xe9, 0x03, 0x65, - 0x92, 0xee, 0xb3, 0xfe, 0x99, 0x85, 0xb4, 0xbd, 0xbf, 0x69, 0x50, 0x5f, 0xed, 0x67, 0x5c, 0x87, - 0x6a, 0xaf, 0x3f, 0x36, 0x3b, 0x03, 0xab, 0x87, 0x36, 0x30, 0x82, 0xfa, 0x53, 0xcb, 0x76, 0x3a, - 0x83, 0x51, 0xf7, 0xc5, 0xf0, 0xf4, 0x18, 0x69, 0x78, 0x1b, 0xd0, 0x02, 0x71, 0x3a, 0xe7, 0x8e, - 0x44, 0x0b, 0xf8, 0x21, 0x3c, 0x18, 0x5b, 0xb6, 0x33, 0x30, 0x6d, 0x6b, 0x6c, 0x3b, 0xfd, 0xa1, - 0x73, 0x6c, 0xd9, 0x66, 0xcf, 0xb4, 0x4d, 0x54, 0xc4, 0x0f, 0x00, 0xe7, 0xe7, 0x3a, 0xa3, 0xde, - 0x39, 0xd2, 0xa5, 0xf6, 0x99, 0x45, 0xfa, 0x47, 0xfd, 0xae, 0x29, 0x77, 0x47, 0x25, 0xc9, 0x94, - 0xda, 0x96, 0x49, 0x06, 0x7d, 0xc9, 0x55, 0x9b, 0xa0, 0xb2, 0xf4, 0xd1, 0xf1, 0x69, 0x67, 0xdc, - 0x25, 0xfd, 0x8e, 0x85, 0x2a, 0xd2, 0x47, 0x4f, 0x87, 0x4b, 0xa0, 0x8a, 0xef, 0xc3, 0xd6, 0x0a, - 0xe0, 0x98, 0x83, 0x01, 0xaa, 0xed, 0xfd, 0x41, 0x83, 0xcd, 0x95, 0xd2, 0x4a, 0x91, 0xe1, 0xc8, - 0x49, 0x91, 0xf4, 0x64, 0xe9, 0x19, 0xd2, 0xb8, 0x90, 0x86, 0x31, 0x34, 0x53, 0x64, 0xbe, 0x3f, - 0x2a, 0x60, 0x80, 0x32, 0xb1, 0xc6, 0xa7, 0x03, 0x1b, 0x15, 0xf1, 0x3d, 0x68, 0x2c, 0xd2, 0xe9, - 0x98, 0xe4, 0x29, 0xd2, 0xa5, 0xe1, 0xf7, 0x7b, 0xd6, 0xd0, 0xee, 0x1f, 0xf5, 0x2d, 0x82, 0x4a, - 0x92, 0xd2, 0xb3, 0x8e, 0xcc, 0xd3, 0x81, 0xed, 0x9c, 0x99, 0x83, 0x53, 0x0b, 0x95, 0x25, 0x25, - 0x55, 0x7d, 0x66, 0x8e, 0x9f, 0xa1, 0xca, 0xde, 0xef, 0x96, 0x61, 0xc9, 0xbc, 0xe3, 0x1a, 0x94, - 0xac, 0xe3, 0x13, 0xfb, 0x3c, 0x0d, 0x49, 0xcd, 0xc8, 0xb4, 0x4a, 0x7d, 0x4d, 0x1e, 0x2c, 0x45, - 0xba, 0xe6, 0x70, 0x34, 0xec, 0x77, 0xcd, 0x01, 0x2a, 0xc8, 0x0a, 0xa4, 0x60, 0xaf, 0xaf, 0xca, - 0x66, 0x92, 0x73, 0x54, 0xc4, 0x6d, 0xf8, 0xde, 0xbb, 0xa8, 0x33, 0x22, 0xce, 0x88, 0xf4, 0x2c, - 0x62, 0xf5, 0x90, 0x2e, 0xcb, 0x9e, 0xc5, 0x86, 0xca, 0x1d, 0xeb, 0x4f, 0xaf, 0x76, 0xb4, 0x6f, - 0x5e, 0xed, 0x68, 0xdf, 0xbe, 0xda, 0xd1, 0xfe, 0xf1, 0x6a, 0x47, 0xfb, 0xfd, 0xeb, 0x9d, 0x8d, - 0x6f, 0x5f, 0xef, 0x6c, 0xfc, 0xfd, 0xf5, 0xce, 0xc6, 0x6f, 0xbe, 0x98, 0x72, 0x71, 0x95, 0x5c, - 0xec, 0xbb, 0xc1, 0xec, 0x20, 0xf7, 0x97, 0xea, 0xfa, 0xc9, 0xc1, 0xcb, 0xf4, 0x7f, 0x95, 0xbc, - 0x53, 0xf1, 0x45, 0x59, 0xfd, 0x4d, 0x7a, 0xf2, 0xbf, 0x00, 0x00, 0x00, 0xff, 0xff, 0xfb, 0x22, - 0x4b, 0xc8, 0x79, 0x0d, 0x00, 0x00, + 0x9f, 0xfb, 0x0f, 0xf4, 0x50, 0xa0, 0xff, 0x42, 0x81, 0x05, 0x0a, 0xf4, 0xbf, 0xd8, 0xe3, 0xde, + 0xda, 0x93, 0x51, 0x24, 0x87, 0x02, 0x39, 0xe6, 0x52, 0xf4, 0x50, 0xa0, 0x98, 0x21, 0xf5, 0x83, + 0x89, 0xe2, 0x62, 0xf7, 0x24, 0xcd, 0xf7, 0xbe, 0xf9, 0xe6, 0xcd, 0x7b, 0x33, 0xdf, 0x48, 0xf0, + 0x63, 0x8f, 0x5e, 0x53, 0x9f, 0x89, 0x03, 0xf9, 0x79, 0x10, 0x87, 0xcc, 0x3d, 0xa0, 0x21, 0x77, + 0xdc, 0xc0, 0xf3, 0x98, 0x2b, 0x78, 0xe0, 0xef, 0x87, 0x51, 0x20, 0x02, 0x7c, 0x2f, 0xe3, 0xed, + 0xcb, 0xcf, 0x7d, 0xc9, 0x7b, 0xb8, 0x3d, 0x0d, 0xa6, 0x81, 0x8a, 0x1e, 0xc8, 0x6f, 0x29, 0xd1, + 0xf8, 0x6f, 0x11, 0x1a, 0x66, 0xc8, 0xbb, 0x0b, 0x01, 0xdc, 0x82, 0x0a, 0xf3, 0xe9, 0x85, 0xc7, + 0x26, 0x2d, 0x6d, 0x57, 0x7b, 0x5c, 0x25, 0xf3, 0x21, 0x3e, 0x81, 0xad, 0xe5, 0x42, 0xce, 0x84, + 0x0a, 0xda, 0x2a, 0xec, 0x6a, 0x8f, 0x37, 0x0f, 0x7f, 0xb8, 0xff, 0xde, 0x72, 0xfb, 0x4b, 0xc5, + 0x1e, 0x15, 0xb4, 0xa3, 0x7f, 0x7d, 0xdb, 0xde, 0x20, 0x4d, 0x37, 0x87, 0xe2, 0x3d, 0xd0, 0x69, + 0xc8, 0xe3, 0x56, 0x71, 0xb7, 0xf8, 0x78, 0xf3, 0xf0, 0xc1, 0x1a, 0x19, 0x33, 0xe4, 0x44, 0x71, + 0xf0, 0x13, 0xa8, 0x5c, 0x31, 0x3a, 0x61, 0x51, 0xdc, 0xd2, 0x15, 0xfd, 0xa3, 0x35, 0xf4, 0x67, + 0x8a, 0x41, 0xe6, 0x4c, 0x3c, 0x00, 0xc4, 0xfd, 0x2b, 0x16, 0x71, 0x41, 0x7d, 0x97, 0x39, 0x6a, + 0xb1, 0x92, 0x9a, 0xfd, 0xff, 0x73, 0x26, 0x5b, 0x2b, 0x53, 0x4d, 0x99, 0xc2, 0x00, 0x50, 0x48, + 0xa3, 0x98, 0x39, 0x13, 0x1e, 0x49, 0xde, 0x35, 0x8b, 0x5b, 0xe5, 0x0f, 0xaa, 0x9d, 0x48, 0x6a, + 0x6f, 0xce, 0x24, 0x5b, 0x61, 0x6e, 0x1c, 0xe3, 0x5f, 0x00, 0xb0, 0x97, 0x82, 0xf9, 0x31, 0x0f, + 0xfc, 0xb8, 0x55, 0x51, 0x3a, 0x8f, 0xd6, 0xe8, 0x58, 0x73, 0x12, 0x59, 0xe1, 0x63, 0x0b, 0x1a, + 0xd7, 0x2c, 0xe2, 0x97, 0xdc, 0xa5, 0x42, 0x09, 0x54, 0x95, 0x40, 0x7b, 0x8d, 0xc0, 0xd9, 0x0a, + 0x8f, 0xe4, 0x67, 0x19, 0xbf, 0x87, 0xda, 0x42, 0x1f, 0x63, 0xd0, 0x7d, 0x3a, 0x63, 0xaa, 0xef, + 0x35, 0xa2, 0xbe, 0xe3, 0xcf, 0x41, 0x8f, 0x12, 0x8f, 0xb5, 0x8a, 0xaa, 0xd3, 0xdf, 0x5f, 0x23, + 0x4f, 0x12, 0x8f, 0x11, 0x45, 0xc2, 0x9f, 0x40, 0xc3, 0x4d, 0x9c, 0x59, 0xe2, 0x09, 0x1e, 0x7a, + 0x9c, 0x45, 0x2d, 0x7d, 0x57, 0x7b, 0xac, 0x93, 0xba, 0x9b, 0x1c, 0x2f, 0xb0, 0xe7, 0x7a, 0xb5, + 0x80, 0x8a, 0xc6, 0x23, 0xd0, 0xe5, 0x44, 0xbc, 0x0d, 0xa5, 0x0b, 0x2f, 0x70, 0xbf, 0x54, 0x8b, + 0xea, 0x24, 0x1d, 0x18, 0x5f, 0x69, 0x50, 0x5f, 0x4d, 0x7b, 0x6d, 0x6a, 0xcf, 0x61, 0xeb, 0x9d, + 0x76, 0xdc, 0x71, 0x1e, 0xdf, 0xe9, 0x46, 0x33, 0xdf, 0x0d, 0xfc, 0x33, 0x28, 0x5f, 0x53, 0x2f, + 0x61, 0xf3, 0xb3, 0xf8, 0xf1, 0x87, 0x24, 0xce, 0x24, 0x8b, 0x64, 0xe4, 0xe7, 0x7a, 0x55, 0x47, + 0x25, 0xe3, 0x3f, 0x1a, 0xc0, 0x32, 0x88, 0x1f, 0x41, 0x6d, 0xd1, 0xa8, 0x2c, 0xe1, 0x25, 0x80, + 0x7f, 0x04, 0x4d, 0xf6, 0x32, 0x64, 0xae, 0x60, 0x13, 0x47, 0xa9, 0xa8, 0xa4, 0x6b, 0xa4, 0x31, + 0x47, 0x53, 0x91, 0xcf, 0x60, 0xcb, 0xa3, 0x82, 0xc5, 0xc2, 0x99, 0xf0, 0x58, 0x1d, 0x41, 0xd5, + 0x02, 0x9d, 0x34, 0x53, 0xb8, 0x97, 0xa1, 0x78, 0x08, 0xd5, 0x98, 0xc9, 0xa6, 0x8a, 0x1b, 0x55, + 0xee, 0xe6, 0xe1, 0xe1, 0x9d, 0xb9, 0xe7, 0x8e, 0xc3, 0x38, 0x9b, 0x49, 0x16, 0x1a, 0xc6, 0x4f, + 0x60, 0x7b, 0x1d, 0x03, 0x57, 0x41, 0x3f, 0xa2, 0xdc, 0x43, 0x1b, 0x78, 0x13, 0x2a, 0xbf, 0xa1, + 0x91, 0xcf, 0xfd, 0x29, 0xd2, 0x8c, 0xbf, 0x15, 0xa0, 0x99, 0xbf, 0x37, 0xf8, 0x0c, 0x1a, 0xd2, + 0x94, 0xb8, 0x2f, 0x58, 0x74, 0x49, 0xdd, 0xac, 0x69, 0x9d, 0x9f, 0xbe, 0xb9, 0x6d, 0xe7, 0x03, + 0x6f, 0x6f, 0xdb, 0x8f, 0x66, 0x34, 0x8c, 0x45, 0x94, 0xb8, 0x22, 0x89, 0xd8, 0x17, 0x46, 0x2e, + 0x6c, 0x90, 0x3a, 0x0d, 0x79, 0x7f, 0x3e, 0x94, 0xba, 0x2a, 0xe6, 0x53, 0xcf, 0x09, 0xa9, 0xb8, + 0x4a, 0x0b, 0x97, 0xea, 0xe6, 0x02, 0xef, 0xeb, 0xe6, 0xc2, 0x06, 0xa9, 0xcf, 0xc7, 0x27, 0x54, + 0x5c, 0xe1, 0x27, 0xa0, 0x8b, 0x9b, 0x30, 0xad, 0x6f, 0xad, 0xd3, 0x7e, 0x73, 0xdb, 0x56, 0xe3, + 0xb7, 0xb7, 0xed, 0xfb, 0x79, 0x15, 0x89, 0x1a, 0x44, 0x05, 0xf1, 0x17, 0x50, 0xa6, 0x93, 0x89, + 0x13, 0xf8, 0xaa, 0xe8, 0xb5, 0xce, 0x27, 0x6f, 0x6e, 0xdb, 0x19, 0xf2, 0xf6, 0xb6, 0xfd, 0xbd, + 0x77, 0xb6, 0xa5, 0x70, 0x83, 0x94, 0xe8, 0x64, 0x32, 0xf2, 0x8d, 0x7f, 0x69, 0x50, 0x4e, 0x9d, + 0x6a, 0xed, 0xb9, 0xfe, 0x39, 0xe8, 0x5f, 0x72, 0x7f, 0xa2, 0xb6, 0xd7, 0x3c, 0xfc, 0xf4, 0x83, + 0x36, 0x97, 0x7d, 0xd8, 0x37, 0x21, 0x23, 0x6a, 0x06, 0xee, 0x40, 0xfd, 0x32, 0xf1, 0x53, 0x7f, + 0x16, 0x74, 0xaa, 0x76, 0xd4, 0x5c, 0xeb, 0x09, 0x47, 0xa7, 0xc3, 0xae, 0xdd, 0x1f, 0x0d, 0x1d, + 0xdb, 0x7c, 0x4a, 0x36, 0xe7, 0x93, 0x6c, 0x3a, 0x35, 0x5e, 0x00, 0x2c, 0x75, 0x71, 0x03, 0x6a, + 0x21, 0x8d, 0x63, 0x27, 0x66, 0xfe, 0x04, 0x6d, 0xe0, 0x26, 0x80, 0x1a, 0x46, 0x2c, 0xf4, 0x6e, + 0x90, 0xb6, 0x08, 0x5f, 0x04, 0xe2, 0x0a, 0x15, 0xf0, 0x16, 0x6c, 0xaa, 0x21, 0x9f, 0xfa, 0x41, + 0xc4, 0x50, 0xd1, 0xf8, 0x77, 0x01, 0x8a, 0x66, 0xc8, 0xef, 0x78, 0x54, 0xe6, 0x05, 0x28, 0xac, + 0x14, 0x40, 0xda, 0x48, 0x30, 0x0b, 0x13, 0xc1, 0x9c, 0xc4, 0xe7, 0x22, 0xce, 0x4e, 0x7e, 0x3d, + 0x03, 0x4f, 0x25, 0x86, 0xf7, 0xe1, 0x3e, 0x7b, 0x29, 0x22, 0xea, 0xe4, 0xa9, 0xa9, 0xe3, 0xdc, + 0x53, 0xa1, 0xee, 0x2a, 0xdf, 0x84, 0xaa, 0x4b, 0x05, 0x9b, 0x06, 0xd1, 0x4d, 0xab, 0xac, 0x6c, + 0x62, 0x5d, 0x5d, 0xc6, 0x21, 0x73, 0xbb, 0x19, 0x2d, 0x7b, 0xb4, 0x16, 0xd3, 0x70, 0x1f, 0x1a, + 0xca, 0x9e, 0x1c, 0x69, 0x1e, 0xdc, 0x9f, 0xb6, 0x2a, 0x4a, 0x67, 0x67, 0x8d, 0x4e, 0x47, 0xf2, + 0xd4, 0xa5, 0x8b, 0x32, 0x99, 0xfa, 0xc5, 0x1c, 0xe2, 0xfe, 0x14, 0x7f, 0x0c, 0x20, 0xf8, 0x8c, + 0x05, 0x89, 0x70, 0x66, 0xd2, 0xbb, 0x65, 0xd2, 0xb5, 0x0c, 0x39, 0x8e, 0xf1, 0xaf, 0xa1, 0xa2, + 0x0c, 0x2a, 0x8a, 0x5b, 0x35, 0xe5, 0x47, 0xbb, 0x6b, 0xd6, 0x78, 0xca, 0x7c, 0x16, 0x71, 0x37, + 0xb7, 0xca, 0x7c, 0x9a, 0xf1, 0x55, 0x01, 0x9a, 0x79, 0xcf, 0x7b, 0xef, 0x74, 0x68, 0xdf, 0xfe, + 0x74, 0xe0, 0xcf, 0xe1, 0xde, 0x52, 0x83, 0xcd, 0x42, 0x69, 0x46, 0x59, 0xef, 0xd0, 0x82, 0x97, + 0xe1, 0xf8, 0x05, 0x34, 0x23, 0x16, 0x27, 0x9e, 0x58, 0x14, 0xac, 0xf8, 0x2d, 0x0a, 0xd6, 0x48, + 0xe7, 0xce, 0x2b, 0xf6, 0x11, 0x54, 0xa5, 0x3b, 0xa8, 0xc3, 0xa2, 0xae, 0x1c, 0xa9, 0xd0, 0x90, + 0x0f, 0xe5, 0x79, 0x59, 0xa9, 0x56, 0xe9, 0xbb, 0x55, 0xeb, 0xaf, 0x1a, 0x6c, 0xae, 0x64, 0x20, + 0xdb, 0x93, 0x86, 0x1c, 0x1a, 0xc9, 0x42, 0x15, 0xa5, 0x87, 0xa7, 0x88, 0x19, 0x4d, 0xf1, 0xaf, + 0xe4, 0x39, 0x57, 0x61, 0xb9, 0xe7, 0xec, 0xa2, 0xae, 0xdb, 0xd5, 0x89, 0x49, 0xc6, 0x16, 0x71, + 0x64, 0x3d, 0x49, 0xa6, 0x78, 0x94, 0xf8, 0xae, 0x3c, 0xe1, 0x13, 0x76, 0x49, 0x65, 0x69, 0xd2, + 0x37, 0x40, 0x79, 0x0f, 0xa9, 0x67, 0x60, 0xfa, 0x04, 0x3c, 0x84, 0x2a, 0xf3, 0xdd, 0x60, 0x22, + 0x0b, 0x97, 0xee, 0x78, 0x31, 0x36, 0xfe, 0xac, 0x41, 0x23, 0xb7, 0xa3, 0x45, 0xca, 0xa9, 0x35, + 0x66, 0xcf, 0x8e, 0x42, 0x94, 0xc9, 0x6d, 0x43, 0x69, 0xf5, 0xb5, 0x49, 0x07, 0xf8, 0x97, 0xf3, + 0x49, 0x0b, 0x03, 0xbc, 0x73, 0x1f, 0xf6, 0xf9, 0x89, 0x95, 0x89, 0x2a, 0x77, 0xc0, 0xd9, 0x8f, + 0x83, 0x34, 0x3b, 0xf5, 0x5d, 0x3d, 0xdd, 0xab, 0xb7, 0x08, 0x7f, 0x2a, 0xf7, 0x2a, 0x58, 0x34, + 0xe3, 0x3e, 0x8f, 0x05, 0x77, 0x33, 0x07, 0xc8, 0x83, 0x32, 0x3f, 0x2f, 0x70, 0xa9, 0xa7, 0xf2, + 0xab, 0x92, 0x74, 0x80, 0x0d, 0xa8, 0xc7, 0xc9, 0x45, 0xec, 0x46, 0x3c, 0x94, 0x27, 0x4b, 0x65, + 0x58, 0x25, 0x39, 0x4c, 0x96, 0x29, 0x16, 0x54, 0xb0, 0xcb, 0xc4, 0x53, 0x89, 0x34, 0xc8, 0x62, + 0x8c, 0xdb, 0xb0, 0x79, 0x45, 0xfd, 0x29, 0xf7, 0xa7, 0xf2, 0xb7, 0x5f, 0xab, 0xa4, 0xa6, 0x43, + 0x06, 0x99, 0x21, 0xdf, 0x33, 0xa0, 0x66, 0xfd, 0xd6, 0xb6, 0x86, 0xe3, 0xfe, 0x68, 0x28, 0x9f, + 0xb8, 0xe1, 0x68, 0x68, 0xa5, 0x4f, 0x9c, 0x49, 0xba, 0xcf, 0xfa, 0x67, 0x16, 0xd2, 0xf6, 0xfe, + 0xae, 0x41, 0x7d, 0xf5, 0x46, 0xe0, 0x3a, 0x54, 0x7b, 0xfd, 0xb1, 0xd9, 0x19, 0x58, 0x3d, 0xb4, + 0x81, 0x11, 0xd4, 0x9f, 0x5a, 0xb6, 0xd3, 0x19, 0x8c, 0xba, 0x2f, 0x86, 0xa7, 0xc7, 0x48, 0xc3, + 0xdb, 0x80, 0x16, 0x88, 0xd3, 0x39, 0x77, 0x24, 0x5a, 0xc0, 0x0f, 0xe1, 0xc1, 0xd8, 0xb2, 0x9d, + 0x81, 0x69, 0x5b, 0x63, 0xdb, 0xe9, 0x0f, 0x9d, 0x63, 0xcb, 0x36, 0x7b, 0xa6, 0x6d, 0xa2, 0x22, + 0x7e, 0x00, 0x38, 0x1f, 0xeb, 0x8c, 0x7a, 0xe7, 0x48, 0x97, 0xda, 0x67, 0x16, 0xe9, 0x1f, 0xf5, + 0xbb, 0xa6, 0x5c, 0x1d, 0x95, 0x24, 0x53, 0x6a, 0x5b, 0x26, 0x19, 0xf4, 0x25, 0x57, 0x2d, 0x82, + 0xca, 0xd2, 0x89, 0xc7, 0xa7, 0x9d, 0x71, 0x97, 0xf4, 0x3b, 0x16, 0xaa, 0x48, 0x27, 0x3e, 0x1d, + 0x2e, 0x81, 0x2a, 0xbe, 0x0f, 0x5b, 0x2b, 0x80, 0x63, 0x0e, 0x06, 0xa8, 0xb6, 0xf7, 0x27, 0x0d, + 0x36, 0x57, 0x5a, 0x2b, 0x45, 0x86, 0x23, 0x27, 0x45, 0xd2, 0x9d, 0xa5, 0x7b, 0x48, 0xf3, 0x42, + 0x1a, 0xc6, 0xd0, 0x4c, 0x91, 0xf9, 0xfa, 0xa8, 0x80, 0x01, 0xca, 0xc4, 0x1a, 0x9f, 0x0e, 0x6c, + 0x54, 0xc4, 0xf7, 0xa0, 0xb1, 0x28, 0xa7, 0x63, 0x92, 0xa7, 0x48, 0x97, 0x4f, 0x46, 0xbf, 0x67, + 0x0d, 0xed, 0xfe, 0x51, 0xdf, 0x22, 0xa8, 0x24, 0x29, 0x3d, 0xeb, 0xc8, 0x3c, 0x1d, 0xd8, 0xce, + 0x99, 0x39, 0x38, 0xb5, 0x50, 0x59, 0x52, 0x52, 0xd5, 0x67, 0xe6, 0xf8, 0x19, 0xaa, 0xec, 0xfd, + 0x61, 0x99, 0x96, 0xac, 0x3b, 0xae, 0x41, 0xc9, 0x3a, 0x3e, 0xb1, 0xcf, 0xd3, 0x94, 0x54, 0x44, + 0x96, 0x55, 0xea, 0x6b, 0x72, 0x63, 0x29, 0xd2, 0x35, 0x87, 0xa3, 0x61, 0xbf, 0x6b, 0x0e, 0x50, + 0x41, 0x76, 0x20, 0x05, 0x7b, 0x7d, 0xd5, 0x36, 0x93, 0x9c, 0xa3, 0x22, 0x6e, 0xc3, 0x0f, 0xde, + 0x45, 0x9d, 0x11, 0x71, 0x46, 0xa4, 0x67, 0x11, 0xab, 0x87, 0x74, 0xd9, 0xf6, 0x2c, 0x37, 0x54, + 0xee, 0x58, 0x7f, 0x79, 0xb5, 0xa3, 0x7d, 0xfd, 0x6a, 0x47, 0xfb, 0xe6, 0xd5, 0x8e, 0xf6, 0xcf, + 0x57, 0x3b, 0xda, 0x1f, 0x5f, 0xef, 0x6c, 0x7c, 0xf3, 0x7a, 0x67, 0xe3, 0x1f, 0xaf, 0x77, 0x36, + 0x7e, 0xf7, 0xd9, 0x94, 0x8b, 0xab, 0xe4, 0x62, 0xdf, 0x0d, 0x66, 0x07, 0xb9, 0x3f, 0x65, 0xd7, + 0x4f, 0x0e, 0x5e, 0xa6, 0xff, 0xcc, 0xe4, 0x9d, 0x8a, 0x2f, 0xca, 0xea, 0x8f, 0xd6, 0x93, 0xff, + 0x05, 0x00, 0x00, 0xff, 0xff, 0xe6, 0xb2, 0x04, 0x99, 0xbb, 0x0d, 0x00, 0x00, } func (this *ApiCollection) Equal(that interface{}) bool { @@ -1545,6 +1553,14 @@ func (this *ParseDirective) Equal(that interface{}) bool { if this.ApiName != that1.ApiName { return false } + if len(this.Parsers) != len(that1.Parsers) { + return false + } + for i := range this.Parsers { + if !this.Parsers[i].Equal(&that1.Parsers[i]) { + return false + } + } return true } func (this *BlockParser) Equal(that interface{}) bool { @@ -2159,6 +2175,20 @@ func (m *ParseDirective) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if len(m.Parsers) > 0 { + for iNdEx := len(m.Parsers) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Parsers[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintApiCollection(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x2a + } + } if len(m.ApiName) > 0 { i -= len(m.ApiName) copy(dAtA[i:], m.ApiName) @@ -2596,6 +2626,12 @@ func (m *ParseDirective) Size() (n int) { if l > 0 { n += 1 + l + sovApiCollection(uint64(l)) } + if len(m.Parsers) > 0 { + for _, e := range m.Parsers { + l = e.Size() + n += 1 + l + sovApiCollection(uint64(l)) + } + } return n } @@ -4198,6 +4234,40 @@ func (m *ParseDirective) Unmarshal(dAtA []byte) error { } m.ApiName = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Parsers", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApiCollection + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthApiCollection + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthApiCollection + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Parsers = append(m.Parsers, GenericParser{}) + if err := m.Parsers[len(m.Parsers)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipApiCollection(dAtA[iNdEx:]) From 0da0cd2bfb1993ed498783fdc2de5403e19c2041 Mon Sep 17 00:00:00 2001 From: oren-lava <111131399+oren-lava@users.noreply.github.com> Date: Wed, 9 Oct 2024 12:07:45 +0300 Subject: [PATCH 3/6] fix: IPRPC pool rewards distribution (#1678) * fix iprpc distribution * fix unit tests * move the check of unstaked providers to specCuMap construction func * fix * fix comments * fix bug in UnstakeEntryForce * add panic when failing to distribute IPRPC rewards and remove claimable rewards --------- Co-authored-by: Oren Co-authored-by: Yaroms <103432884+Yaroms@users.noreply.github.com> --- x/dualstaking/keeper/delegator_reward.go | 31 ++++++++++++------- x/pairing/keeper/cu_tracker_test.go | 13 +++++--- x/pairing/keeper/delegator_rewards_test.go | 6 ++-- .../grpc_query_provider_monthly_payout.go | 2 +- x/pairing/keeper/unstaking.go | 20 ++++++------ x/pairing/types/expected_keepers.go | 2 +- x/rewards/keeper/iprpc.go | 6 ++-- x/rewards/keeper/providers.go | 10 ++++-- x/rewards/types/expected_keepers.go | 2 +- x/subscription/keeper/cu_tracker.go | 2 +- x/subscription/types/expected_keepers.go | 2 +- 11 files changed, 54 insertions(+), 42 deletions(-) diff --git a/x/dualstaking/keeper/delegator_reward.go b/x/dualstaking/keeper/delegator_reward.go index a1d428ee26..5decaf9a7e 100644 --- a/x/dualstaking/keeper/delegator_reward.go +++ b/x/dualstaking/keeper/delegator_reward.go @@ -1,6 +1,7 @@ package keeper import ( + "fmt" "strconv" "cosmossdk.io/math" @@ -158,47 +159,55 @@ func (k Keeper) ClaimRewards(ctx sdk.Context, delegator string, provider string) // RewardProvidersAndDelegators is the main function handling provider rewards with delegations // it returns the provider reward amount and updates the delegatorReward map with the reward portion for each delegator -func (k Keeper) RewardProvidersAndDelegators(ctx sdk.Context, provider string, chainID string, totalReward sdk.Coins, senderModule string, calcOnlyProvider bool, calcOnlyDelegators bool, calcOnlyContributor bool) (providerReward sdk.Coins, claimableRewards sdk.Coins, err error) { +// since this function does not actually send rewards to the providers and delegator (but only allocates rewards to be claimed) +func (k Keeper) RewardProvidersAndDelegators(ctx sdk.Context, provider string, chainID string, totalReward sdk.Coins, senderModule string, calcOnlyProvider bool, calcOnlyDelegators bool, calcOnlyContributor bool) (providerReward sdk.Coins, err error) { block := uint64(ctx.BlockHeight()) zeroCoins := sdk.NewCoins() epoch, _, err := k.epochstorageKeeper.GetEpochStartForBlock(ctx, block) if err != nil { - return zeroCoins, zeroCoins, utils.LavaFormatError(types.ErrCalculatingProviderReward.Error(), err, + return zeroCoins, utils.LavaFormatError(types.ErrCalculatingProviderReward.Error(), err, utils.Attribute{Key: "block", Value: block}, ) } stakeEntry, found := k.epochstorageKeeper.GetStakeEntry(ctx, epoch, chainID, provider) if !found { - return zeroCoins, zeroCoins, err + return zeroCoins, utils.LavaFormatWarning("RewardProvidersAndDelegators: cannot send rewards to provider and delegators", fmt.Errorf("provider stake entry not found, probably unstaked"), + utils.LogAttr("epoch", epoch), + utils.LogAttr("provider", provider), + utils.LogAttr("chain_id", chainID), + utils.LogAttr("sender_module", senderModule), + utils.LogAttr("reward", totalReward.String()), + ) } delegations, err := k.GetProviderDelegators(ctx, provider, epoch) if err != nil { - return zeroCoins, zeroCoins, utils.LavaFormatError("cannot get provider's delegators", err) + return zeroCoins, utils.LavaFormatError("cannot get provider's delegators", err) } - claimableRewards = totalReward // make sure this is post boost when rewards pool is introduced contributorAddresses, contributorPart := k.specKeeper.GetContributorReward(ctx, chainID) contributorsNum := sdk.NewInt(int64(len(contributorAddresses))) + contributorReward := zeroCoins if !contributorsNum.IsZero() && contributorPart.GT(math.LegacyZeroDec()) { - contributorReward := totalReward.MulInt(contributorPart.MulInt64(spectypes.ContributorPrecision).RoundInt()).QuoInt(sdk.NewInt(spectypes.ContributorPrecision)) + contributorReward = totalReward.MulInt(contributorPart.MulInt64(spectypes.ContributorPrecision).RoundInt()).QuoInt(sdk.NewInt(spectypes.ContributorPrecision)) // make sure to round it down for the integers division contributorReward = contributorReward.QuoInt(contributorsNum).MulInt(contributorsNum) - claimableRewards = totalReward.Sub(contributorReward...) if !calcOnlyContributor { err = k.PayContributors(ctx, senderModule, contributorAddresses, contributorReward, chainID) if err != nil { - return zeroCoins, zeroCoins, err + return zeroCoins, err } } } + // delegators eligible for rewards are delegators that their delegation is at least a week old relevantDelegations := lavaslices.Filter(delegations, func(d types.Delegation) bool { return d.ChainID == chainID && d.IsFirstWeekPassed(ctx.BlockTime().UTC().Unix()) && d.Delegator != stakeEntry.Vault }) - providerReward, delegatorsReward := k.CalcRewards(ctx, stakeEntry, claimableRewards, relevantDelegations) + // calculate the rewards for the providers and delegators (total reward - contributors reward) + providerReward, delegatorsReward := k.CalcRewards(ctx, stakeEntry, totalReward.Sub(contributorReward...), relevantDelegations) leftoverRewards := k.updateDelegatorsReward(ctx, stakeEntry.DelegateTotal.Amount, relevantDelegations, delegatorsReward, senderModule, calcOnlyDelegators) fullProviderReward := providerReward.Add(leftoverRewards...) @@ -208,7 +217,7 @@ func (k Keeper) RewardProvidersAndDelegators(ctx sdk.Context, provider string, c k.rewardDelegator(ctx, types.Delegation{Provider: stakeEntry.Address, ChainID: chainID, Delegator: stakeEntry.Vault}, fullProviderReward, senderModule) } - return fullProviderReward, claimableRewards, nil + return fullProviderReward, nil } // updateDelegatorsReward updates the delegator rewards map @@ -217,11 +226,9 @@ func (k Keeper) updateDelegatorsReward(ctx sdk.Context, totalDelegations math.In for _, delegation := range delegations { delegatorReward := k.CalcDelegatorReward(ctx, delegatorsReward, totalDelegations, delegation) - if !calcOnly { k.rewardDelegator(ctx, delegation, delegatorReward, senderModule) } - usedDelegatorRewards = usedDelegatorRewards.Add(delegatorReward...) } diff --git a/x/pairing/keeper/cu_tracker_test.go b/x/pairing/keeper/cu_tracker_test.go index eef6daccc7..cc5454d765 100644 --- a/x/pairing/keeper/cu_tracker_test.go +++ b/x/pairing/keeper/cu_tracker_test.go @@ -641,13 +641,16 @@ func TestProviderMonthlyPayoutQueryWithContributor(t *testing.T) { } ts.relayPaymentWithoutPay(relayPaymentMessage, true) - // check for expected balance: planPrice*100/200 (from spec1) + planPrice*(100/200)*(2/3) (from spec, considering delegations) - // for planPrice=100, expected monthly payout is 50 (spec1 with contributor) + 33 (normal spec no contributor) - expectedContributorPay := uint64(12) // half the plan payment for spec1:25 then divided between contributors half half rounded down - expectedTotalPayout := uint64(83) - expectedContributorPay*2 + // half the plan payment for spec1 is 25. Then it's divided between 2 contributors equally rounded down + expectedContributorPay := uint64(12) + + // for planPrice=100, and equal CU usage for both specs, the expected provider monthly payout is: + // spec (delegator is 33% of stake): planPrice * specUsedCu/totalUsedCu * providerStake/totalStake = 100*0.5*(2/3) = 33 + // spec1 (contributors with commission=50%): planPrice * specUsedCu/totalUsedCu - contributorsPart = 100*0.5 - 24 = 26 + expectedTotalPayout := uint64(59) expectedPayouts := []types.SubscriptionPayout{ {Subscription: clientAcc.Addr.String(), ChainId: ts.spec.Index, Amount: 33}, - {Subscription: clientAcc.Addr.String(), ChainId: spec1.Index, Amount: 26}, // 50 - 26 for contributors (each contributor gets 12) + {Subscription: clientAcc.Addr.String(), ChainId: spec1.Index, Amount: 26}, } res, err := ts.QueryPairingProviderMonthlyPayout(provider) require.NoError(t, err) diff --git a/x/pairing/keeper/delegator_rewards_test.go b/x/pairing/keeper/delegator_rewards_test.go index fd9ba80d3c..a8049e9811 100644 --- a/x/pairing/keeper/delegator_rewards_test.go +++ b/x/pairing/keeper/delegator_rewards_test.go @@ -541,7 +541,7 @@ func TestDelegationFirstMonthReward(t *testing.T) { // this, we'll call the reward calculation function directly with a fabricated reward just to // verify that the delegator gets nothing from the total reward fakeReward := sdk.NewCoins(sdk.NewCoin(ts.TokenDenom(), sdk.NewInt(testStake))) - providerReward, _, err := ts.Keepers.Dualstaking.RewardProvidersAndDelegators(ts.Ctx, provider, ts.spec.Index, + providerReward, err := ts.Keepers.Dualstaking.RewardProvidersAndDelegators(ts.Ctx, provider, ts.spec.Index, fakeReward, subscriptiontypes.ModuleName, true, true, true) require.NoError(t, err) require.True(t, fakeReward.IsEqual(providerReward)) // if the delegator got anything, this would fail @@ -603,11 +603,11 @@ func TestRedelegationFirstMonthReward(t *testing.T) { // verify that the delegator gets nothing from the total reward from provider1 but does get // reward from provider fakeReward := sdk.NewCoins(sdk.NewCoin(ts.TokenDenom(), sdk.NewInt(testStake))) - provider1Reward, _, err := ts.Keepers.Dualstaking.RewardProvidersAndDelegators(ts.Ctx, provider1, ts.spec.Index, + provider1Reward, err := ts.Keepers.Dualstaking.RewardProvidersAndDelegators(ts.Ctx, provider1, ts.spec.Index, fakeReward, subscriptiontypes.ModuleName, true, false, true) require.NoError(t, err) require.True(t, fakeReward.IsEqual(provider1Reward)) // if the delegator got anything, this would fail - providerReward, _, err := ts.Keepers.Dualstaking.RewardProvidersAndDelegators(ts.Ctx, provider, ts.spec.Index, + providerReward, err := ts.Keepers.Dualstaking.RewardProvidersAndDelegators(ts.Ctx, provider, ts.spec.Index, fakeReward, subscriptiontypes.ModuleName, true, false, true) require.NoError(t, err) require.False(t, fakeReward.IsEqual(providerReward)) // the delegator should have rewards diff --git a/x/pairing/keeper/grpc_query_provider_monthly_payout.go b/x/pairing/keeper/grpc_query_provider_monthly_payout.go index 7d5759fffa..1268a2bd82 100644 --- a/x/pairing/keeper/grpc_query_provider_monthly_payout.go +++ b/x/pairing/keeper/grpc_query_provider_monthly_payout.go @@ -73,7 +73,7 @@ func (k Keeper) ProviderMonthlyPayout(goCtx context.Context, req *types.QueryPro providerRewardAfterFees := sdk.NewCoins(sdk.NewCoin(k.stakingKeeper.BondDenom(ctx), totalMonthlyReward.Sub(validatorsFee.AmountOf(denom)).Sub(communityFee.AmountOf(denom)))) // calculate only the provider reward - providerReward, _, err := k.dualstakingKeeper.RewardProvidersAndDelegators(ctx, req.Provider, chainID, providerRewardAfterFees, subsciptiontypes.ModuleName, true, true, true) + providerReward, err := k.dualstakingKeeper.RewardProvidersAndDelegators(ctx, req.Provider, chainID, providerRewardAfterFees, subsciptiontypes.ModuleName, true, true, true) if err != nil { return nil, err } diff --git a/x/pairing/keeper/unstaking.go b/x/pairing/keeper/unstaking.go index ff4f6eafb0..87d64d5174 100644 --- a/x/pairing/keeper/unstaking.go +++ b/x/pairing/keeper/unstaking.go @@ -68,13 +68,6 @@ func (k Keeper) UnstakeEntry(ctx sdk.Context, validator, chainID, creator, unsta } func (k Keeper) UnstakeEntryForce(ctx sdk.Context, chainID, provider, unstakeDescription string) error { - providerAddr, err := sdk.AccAddressFromBech32(provider) - if err != nil { - return utils.LavaFormatWarning("invalid address", err, - utils.Attribute{Key: "provider", Value: provider}, - ) - } - existingEntry, entryExists := k.epochStorageKeeper.GetStakeEntryCurrent(ctx, chainID, provider) if !entryExists { return utils.LavaFormatWarning("can't unstake Entry, stake entry not found for address", fmt.Errorf("stake entry not found"), @@ -83,7 +76,15 @@ func (k Keeper) UnstakeEntryForce(ctx sdk.Context, chainID, provider, unstakeDes ) } totalAmount := existingEntry.Stake.Amount - delegations := k.stakingKeeper.GetAllDelegatorDelegations(ctx, providerAddr) + vaultAcc, err := sdk.AccAddressFromBech32(existingEntry.Vault) + if err != nil { + return utils.LavaFormatError("can't unstake entry, invalid vault address", err, + utils.LogAttr("provider", provider), + utils.LogAttr("chain", chainID), + utils.LogAttr("vault", existingEntry.Vault), + ) + } + delegations := k.stakingKeeper.GetAllDelegatorDelegations(ctx, vaultAcc) for _, delegation := range delegations { validator, found := k.stakingKeeper.GetValidator(ctx, delegation.GetValidatorAddr()) @@ -105,9 +106,6 @@ func (k Keeper) UnstakeEntryForce(ctx sdk.Context, chainID, provider, unstakeDes } if totalAmount.IsZero() { - existingEntry, _ := k.epochStorageKeeper.GetStakeEntryCurrent(ctx, chainID, provider) - k.epochStorageKeeper.RemoveStakeEntryCurrent(ctx, chainID, existingEntry.Address) - details := map[string]string{ "address": existingEntry.GetAddress(), "chainID": existingEntry.GetChain(), diff --git a/x/pairing/types/expected_keepers.go b/x/pairing/types/expected_keepers.go index 6a8fb13978..b9ba3bed29 100644 --- a/x/pairing/types/expected_keepers.go +++ b/x/pairing/types/expected_keepers.go @@ -102,7 +102,7 @@ type DowntimeKeeper interface { } type DualstakingKeeper interface { - RewardProvidersAndDelegators(ctx sdk.Context, providerAddr string, chainID string, totalReward sdk.Coins, senderModule string, calcOnlyProvider bool, calcOnlyDelegators bool, calcOnlyContributor bool) (providerReward sdk.Coins, totalRewards sdk.Coins, err error) + RewardProvidersAndDelegators(ctx sdk.Context, providerAddr string, chainID string, totalReward sdk.Coins, senderModule string, calcOnlyProvider bool, calcOnlyDelegators bool, calcOnlyContributor bool) (providerReward sdk.Coins, err error) DelegateFull(ctx sdk.Context, delegator string, validator string, provider string, chainID string, amount sdk.Coin) error UnbondFull(ctx sdk.Context, delegator string, validator string, provider string, chainID string, amount sdk.Coin, unstake bool) error GetProviderDelegators(ctx sdk.Context, provider string, epoch uint64) ([]dualstakingtypes.Delegation, error) diff --git a/x/rewards/keeper/iprpc.go b/x/rewards/keeper/iprpc.go index 8ac287e452..90d94380c9 100644 --- a/x/rewards/keeper/iprpc.go +++ b/x/rewards/keeper/iprpc.go @@ -132,7 +132,6 @@ func (k Keeper) distributeIprpcRewards(ctx sdk.Context, iprpcReward types.IprpcR k.handleNoIprpcRewardToProviders(ctx, iprpcReward.SpecFunds) return } - leftovers := sdk.NewCoins() for _, specFund := range iprpcReward.SpecFunds { details := map[string]string{} @@ -178,9 +177,10 @@ func (k Keeper) distributeIprpcRewards(ctx sdk.Context, iprpcReward types.IprpcR UsedReward = UsedRewardTemp // reward the provider - _, _, err := k.dualstakingKeeper.RewardProvidersAndDelegators(ctx, providerCU.Provider, specFund.Spec, providerIprpcReward, string(types.IprpcPoolName), false, false, false) + _, err := k.dualstakingKeeper.RewardProvidersAndDelegators(ctx, providerCU.Provider, specFund.Spec, providerIprpcReward, string(types.IprpcPoolName), false, false, false) if err != nil { - utils.LavaFormatError("failed to send iprpc rewards to provider", err, utils.LogAttr("provider", providerCU)) + // failed sending the rewards, add the claimable rewards to the leftovers that will be transferred to the community pool + utils.LavaFormatPanic("failed to send iprpc rewards to provider", err, utils.LogAttr("provider", providerCU)) } details[providerCU.Provider] = fmt.Sprintf("cu: %d reward: %s", providerCU.CU, providerIprpcReward.String()) } diff --git a/x/rewards/keeper/providers.go b/x/rewards/keeper/providers.go index e9a45ab093..43dc4b42f9 100644 --- a/x/rewards/keeper/providers.go +++ b/x/rewards/keeper/providers.go @@ -88,7 +88,7 @@ func (k Keeper) distributeMonthlyBonusRewards(ctx sdk.Context) { return } // now give the reward the provider contributor and delegators - _, _, err := k.dualstakingKeeper.RewardProvidersAndDelegators(ctx, basepay.Provider, basepay.ChainId, sdk.NewCoins(sdk.NewCoin(k.stakingKeeper.BondDenom(ctx), reward)), string(types.ProviderRewardsDistributionPool), false, false, false) + _, err := k.dualstakingKeeper.RewardProvidersAndDelegators(ctx, basepay.Provider, basepay.ChainId, sdk.NewCoins(sdk.NewCoin(k.stakingKeeper.BondDenom(ctx), reward)), string(types.ProviderRewardsDistributionPool), false, false, false) if err != nil { utils.LavaFormatError("failed to send bonus rewards to provider", err, utils.LogAttr("provider", basepay.Provider)) } @@ -178,10 +178,14 @@ func (k Keeper) specProvidersBasePay(ctx sdk.Context, chainID string, pop bool) } totalBasePay := math.ZeroInt() + stakedBasePays := []types.BasePayWithIndex{} for _, basepay := range basepays { - totalBasePay = totalBasePay.Add(basepay.BasePay.Total) + if _, found := k.epochstorage.GetStakeEntryCurrent(ctx, basepay.ChainId, basepay.Provider); found { + totalBasePay = totalBasePay.Add(basepay.BasePay.Total) + stakedBasePays = append(stakedBasePays, basepay) + } } - return basepays, totalBasePay + return stakedBasePays, totalBasePay } // ContributeToValidatorsAndCommunityPool transfers some of the providers' rewards to the validators and community pool diff --git a/x/rewards/types/expected_keepers.go b/x/rewards/types/expected_keepers.go index a79582f6d4..989c7b78ea 100644 --- a/x/rewards/types/expected_keepers.go +++ b/x/rewards/types/expected_keepers.go @@ -53,7 +53,7 @@ type StakingKeeper interface { } type DualStakingKeeper interface { - RewardProvidersAndDelegators(ctx sdk.Context, providerAddr string, chainID string, totalReward sdk.Coins, senderModule string, calcOnlyProvider bool, calcOnlyDelegators bool, calcOnlyContributor bool) (providerReward sdk.Coins, totalRewards sdk.Coins, err error) + RewardProvidersAndDelegators(ctx sdk.Context, providerAddr string, chainID string, totalReward sdk.Coins, senderModule string, calcOnlyProvider bool, calcOnlyDelegators bool, calcOnlyContributor bool) (providerReward sdk.Coins, err error) // Methods imported from bank should be defined here } diff --git a/x/subscription/keeper/cu_tracker.go b/x/subscription/keeper/cu_tracker.go index a742913b99..6426cffc5b 100644 --- a/x/subscription/keeper/cu_tracker.go +++ b/x/subscription/keeper/cu_tracker.go @@ -195,7 +195,7 @@ func (k Keeper) RewardAndResetCuTracker(ctx sdk.Context, cuTrackerTimerKeyBytes // Note: if the reward function doesn't reward the provider // because he was unstaked, we only print an error and not returning - _, _, err := k.dualstakingKeeper.RewardProvidersAndDelegators(ctx, provider, chainID, sdk.NewCoins(creditToSub), types.ModuleName, false, false, false) + _, err := k.dualstakingKeeper.RewardProvidersAndDelegators(ctx, provider, chainID, sdk.NewCoins(creditToSub), types.ModuleName, false, false, false) if errors.Is(err, epochstoragetypes.ErrProviderNotStaked) || errors.Is(err, epochstoragetypes.ErrStakeStorageNotFound) { utils.LavaFormatWarning("sending provider reward with delegations failed", err, utils.Attribute{Key: "provider", Value: provider}, diff --git a/x/subscription/types/expected_keepers.go b/x/subscription/types/expected_keepers.go index 264fe6f6d1..f459e5e1bc 100644 --- a/x/subscription/types/expected_keepers.go +++ b/x/subscription/types/expected_keepers.go @@ -68,7 +68,7 @@ type TimerStoreKeeper interface { } type DualStakingKeeper interface { - RewardProvidersAndDelegators(ctx sdk.Context, providerAddr string, chainID string, totalReward sdk.Coins, senderModule string, calcOnlyProvider bool, calcOnlyDelegators bool, calcOnlyContributor bool) (providerReward sdk.Coins, totalRewards sdk.Coins, err error) + RewardProvidersAndDelegators(ctx sdk.Context, providerAddr string, chainID string, totalReward sdk.Coins, senderModule string, calcOnlyProvider bool, calcOnlyDelegators bool, calcOnlyContributor bool) (providerReward sdk.Coins, err error) GetDelegation(ctx sdk.Context, delegator, provider, chainID string, epoch uint64) (dualstakingtypes.Delegation, bool) } From efcb0ef660381d0419811a36335d19593eba9246 Mon Sep 17 00:00:00 2001 From: Elad Gildnur <6321801+shleikes@users.noreply.github.com> Date: Wed, 9 Oct 2024 15:19:52 +0300 Subject: [PATCH 4/6] fix: PRT - Add EVMOS missing debug APIs + remove unsupported (#1725) * Replace the method in the debug verification * Fix api_name * EVMOS spec: Remove unsupported debug apis and added new ones * Add minimum CU of 1 (even though it's disabled - should be fixed) * CR Fix: Remove redundant methods --- cookbook/specs/evmos.json | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/cookbook/specs/evmos.json b/cookbook/specs/evmos.json index 81b7f332eb..c0324f3cae 100644 --- a/cookbook/specs/evmos.json +++ b/cookbook/specs/evmos.json @@ -1590,6 +1590,43 @@ "type": "POST", "add_on": "debug" }, + "apis": [ + { + "name": "debug_getBadBlocks", + "compute_units": 1, + "enabled": false + }, + { + "name": "debug_getRawBlock", + "compute_units": 1, + "enabled": false + }, + { + "name": "debug_getRawHeader", + "compute_units": 1, + "enabled": false + }, + { + "name": "debug_getRawReceipts", + "compute_units": 1, + "enabled": false + }, + { + "name": "debug_getRawTransaction", + "compute_units": 1, + "enabled": false + }, + { + "name": "debug_storageRangeAt", + "compute_units": 1, + "enabled": false + }, + { + "name": "debug_traceCall", + "compute_units": 1, + "enabled": false + } + ], "verifications": [ { "name": "enabled", From a781ec470b5fea0091569054ec53e4892aeb6546 Mon Sep 17 00:00:00 2001 From: Ran Mishael <106548467+ranlavanet@users.noreply.github.com> Date: Wed, 9 Oct 2024 14:38:10 +0200 Subject: [PATCH 5/6] feat: PRT - adding metric to check number of websocket connections active at any given time (#1735) --- .../chainlib/consumer_websocket_manager.go | 4 ++++ protocol/metrics/metrics_consumer_manager.go | 19 +++++++++++++++++++ protocol/metrics/rpcconsumerlogs.go | 4 ++++ 3 files changed, 27 insertions(+) diff --git a/protocol/chainlib/consumer_websocket_manager.go b/protocol/chainlib/consumer_websocket_manager.go index 748fd7e5b3..5f47180489 100644 --- a/protocol/chainlib/consumer_websocket_manager.go +++ b/protocol/chainlib/consumer_websocket_manager.go @@ -96,6 +96,10 @@ func (cwm *ConsumerWebsocketManager) handleRateLimitReached(inpData []byte) ([]b } func (cwm *ConsumerWebsocketManager) ListenToMessages() { + // adding metrics for how many active connections we have. + cwm.rpcConsumerLogs.SetWebSocketConnectionActive(cwm.chainId, cwm.apiInterface, true) + defer cwm.rpcConsumerLogs.SetWebSocketConnectionActive(cwm.chainId, cwm.apiInterface, false) + var ( messageType int msg []byte diff --git a/protocol/metrics/metrics_consumer_manager.go b/protocol/metrics/metrics_consumer_manager.go index 83cd72d025..ce88f2145b 100644 --- a/protocol/metrics/metrics_consumer_manager.go +++ b/protocol/metrics/metrics_consumer_manager.go @@ -44,6 +44,7 @@ type ConsumerMetricsManager struct { totalFailedWsSubscriptionRequestsMetric *prometheus.CounterVec totalWsSubscriptionDissconnectMetric *prometheus.CounterVec totalDuplicatedWsSubscriptionRequestsMetric *prometheus.CounterVec + totalWebSocketConnectionsActive *prometheus.GaugeVec blockMetric *prometheus.GaugeVec latencyMetric *prometheus.GaugeVec qosMetric *prometheus.GaugeVec @@ -113,6 +114,11 @@ func NewConsumerMetricsManager(options ConsumerMetricsManagerOptions) *ConsumerM Help: "The total number of duplicated webscket subscription requests over time per chain id per api interface.", }, []string{"spec", "apiInterface"}) + totalWebSocketConnectionsActive := prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Name: "lava_consumer_total_websocket_connections_active", + Help: "The total number of currently active websocket connections with users", + }, []string{"spec", "apiInterface"}) + totalWsSubscriptionDissconnectMetric := prometheus.NewCounterVec(prometheus.CounterOpts{ Name: "lava_consumer_total_ws_subscription_disconnect", Help: "The total number of websocket subscription disconnects over time per chain id per api interface per dissconnect reason.", @@ -218,6 +224,7 @@ func NewConsumerMetricsManager(options ConsumerMetricsManagerOptions) *ConsumerM prometheus.MustRegister(endpointsHealthChecksOkMetric) prometheus.MustRegister(protocolVersionMetric) prometheus.MustRegister(totalRelaysSentByNewBatchTickerMetric) + prometheus.MustRegister(totalWebSocketConnectionsActive) prometheus.MustRegister(apiSpecificsMetric) prometheus.MustRegister(averageLatencyMetric) prometheus.MustRegister(totalRelaysSentToProvidersMetric) @@ -238,6 +245,7 @@ func NewConsumerMetricsManager(options ConsumerMetricsManagerOptions) *ConsumerM totalFailedWsSubscriptionRequestsMetric: totalFailedWsSubscriptionRequestsMetric, totalDuplicatedWsSubscriptionRequestsMetric: totalDuplicatedWsSubscriptionRequestsMetric, totalWsSubscriptionDissconnectMetric: totalWsSubscriptionDissconnectMetric, + totalWebSocketConnectionsActive: totalWebSocketConnectionsActive, totalErroredMetric: totalErroredMetric, blockMetric: blockMetric, latencyMetric: latencyMetric, @@ -297,6 +305,17 @@ func (pme *ConsumerMetricsManager) SetRelaySentToProviderMetric(chainId string, pme.totalRelaysSentToProvidersMetric.WithLabelValues(chainId, apiInterface).Inc() } +func (pme *ConsumerMetricsManager) SetWebSocketConnectionActive(chainId string, apiInterface string, add bool) { + if pme == nil { + return + } + if add { + pme.totalWebSocketConnectionsActive.WithLabelValues(chainId, apiInterface).Add(1) + } else { + pme.totalWebSocketConnectionsActive.WithLabelValues(chainId, apiInterface).Sub(1) + } +} + func (pme *ConsumerMetricsManager) SetRelayNodeErrorMetric(chainId string, apiInterface string) { if pme == nil { return diff --git a/protocol/metrics/rpcconsumerlogs.go b/protocol/metrics/rpcconsumerlogs.go index 5171faa1bb..dd5c36a3fd 100644 --- a/protocol/metrics/rpcconsumerlogs.go +++ b/protocol/metrics/rpcconsumerlogs.go @@ -87,6 +87,10 @@ func NewRPCConsumerLogs(consumerMetricsManager *ConsumerMetricsManager, consumer return rpcConsumerLogs, err } +func (rpccl *RPCConsumerLogs) SetWebSocketConnectionActive(chainId string, apiInterface string, add bool) { + rpccl.consumerMetricsManager.SetWebSocketConnectionActive(chainId, apiInterface, add) +} + func (rpccl *RPCConsumerLogs) SetRelaySentToProviderMetric(chainId string, apiInterface string) { rpccl.consumerMetricsManager.SetRelaySentToProviderMetric(chainId, apiInterface) } From 62e988fe780f706ca6b948abc0642d7fbb260b84 Mon Sep 17 00:00:00 2001 From: Elad Gildnur <6321801+shleikes@users.noreply.github.com> Date: Wed, 9 Oct 2024 15:38:38 +0300 Subject: [PATCH 6/6] Reduce severity of decode error to debug (#1733) --- protocol/chainlib/grpcproxy/grpcproxy.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protocol/chainlib/grpcproxy/grpcproxy.go b/protocol/chainlib/grpcproxy/grpcproxy.go index 3eee098cfb..d9e86fa48b 100644 --- a/protocol/chainlib/grpcproxy/grpcproxy.go +++ b/protocol/chainlib/grpcproxy/grpcproxy.go @@ -112,7 +112,7 @@ func (RawBytesCodec) Marshal(v interface{}) ([]byte, error) { func (RawBytesCodec) Unmarshal(data []byte, v interface{}) error { bufferPtr, ok := v.(*[]byte) if !ok { - return utils.LavaFormatError("cannot decode into type", nil, utils.LogAttr("v", v), utils.LogAttr("data", data)) + return utils.LavaFormatDebug("cannot decode into type", utils.LogAttr("v", v), utils.LogAttr("data", data)) } *bufferPtr = data return nil