diff --git a/.golangci.yml b/.golangci.yml index e362fef5d2..a6939f5b2d 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -41,6 +41,14 @@ linters: - whitespace issues: + exclude-files: + - "protocol/chainlib/chainproxy/rpcInterfaceMessages/grpcMessage.go" + - "protocol/chainlib/grpc.go" + - "protocol/chainlib/grpcproxy/dyncodec/remote_grpc_reflection.go" + - "protocol/chainlib/grpcproxy/dyncodec/remote_relayer.go" + - "protocol/chainlib/grpcproxy/dyncodec/remotes_test.go" + - "ecosystem/lavajs/*" + - "ecosystem/lava-sdk/*" exclude-rules: - text: "singleCaseSwitch" linters: @@ -56,7 +64,7 @@ issues: - text: "ST1016:" linters: - stylecheck - - text: "SA1019:.*\"github.com/golang/protobuf/proto\" is deprecated.*" # proto is deprecated, but some places couldn't be removed + - text: 'SA1019:.*"github.com/golang/protobuf/proto" is deprecated.*' # proto is deprecated, but some places couldn't be removed linters: - staticcheck - path: "migrations" diff --git a/cookbook/specs/near.json b/cookbook/specs/near.json index 037335089c..05604a66ce 100644 --- a/cookbook/specs/near.json +++ b/cookbook/specs/near.json @@ -430,6 +430,32 @@ } ], "verifications": [ + { + "name": "tracking-shards", + "parse_directive": { + "function_template": "{\"jsonrpc\":\"2.0\",\"id\":\"dontcare\",\"method\":\"query\",\"params\":{\"request_type\":\"view_account\",\"finality\":\"final\",\"account_id\":\"floor.is.lava\"}}", + "function_tag": "VERIFICATION", + "parsers": [ + { + "parse_path": ".error.cause.name", + "value": "UNKNOWN_ACCOUNT", + "parse_type": "RESULT" + }, + { + "parse_path": ".result.amount", + "value": "*", + "parse_type": "RESULT" + } + ], + "api_name": "query" + }, + "values": [ + { + "expected_value": "*", + "severity": "Warning" + } + ] + }, { "name": "chain-id", "parse_directive": { diff --git a/proto/lavanet/lava/spec/api_collection.proto b/proto/lavanet/lava/spec/api_collection.proto index 1dc6b74b92..182cd085f6 100644 --- a/proto/lavanet/lava/spec/api_collection.proto +++ b/proto/lavanet/lava/spec/api_collection.proto @@ -119,14 +119,14 @@ enum FUNCTION_TAG { } enum PARSER_TYPE { - NO_PARSER = 0; - BLOCK_LATEST = 1; - BLOCK_EARLIEST = 2; - RESULT = 3; - EXTENSION_ARG = 4; - IDENTIFIER = 5; - DEFAULT_VALUE = 6; - BLOCK_HASH = 7; + NO_PARSER = 0; // parsing is disabled + BLOCK_LATEST = 1; // parse the latest block + BLOCK_EARLIEST = 2; // parse the earliest block + RESULT = 3; // parse the result of the rpc call + EXTENSION_ARG = 4; // parse the extension argument (e.g. should we turn on an extension or not, based on the parsed value) + IDENTIFIER = 5; // parse the id of the rpc message + DEFAULT_VALUE = 6; // set the default value for the parsed result (currently used for block, after all other generic parsers failed) + BLOCK_HASH = 7; // parse the block hash } enum PARSER_FUNC{ diff --git a/protocol/chainlib/chain_fetcher.go b/protocol/chainlib/chain_fetcher.go index a99408e690..c9fc7f255a 100644 --- a/protocol/chainlib/chain_fetcher.go +++ b/protocol/chainlib/chain_fetcher.go @@ -191,7 +191,18 @@ func (cf *ChainFetcher) Verify(ctx context.Context, verification VerificationCon parsedInput := parser.ParseBlockFromReply(parserInput, parsing.ResultParsing, parsing.Parsers) if parsedInput.GetRawParsedData() == "" { - return utils.LavaFormatWarning("[-] verify failed to parse result", err, + return utils.LavaFormatWarning("[-] verify failed to parse result", nil, + utils.LogAttr("chainId", chainId), + utils.LogAttr("nodeUrl", proxyUrl.Url), + utils.LogAttr("Method", parsing.GetApiName()), + utils.LogAttr("Response", string(reply.RelayReply.Data)), + ) + } + + parserError := parsedInput.GetParserError() + if parserError != "" { + return utils.LavaFormatWarning("[-] parser returned an error", nil, + utils.LogAttr("error", parserError), utils.LogAttr("chainId", chainId), utils.LogAttr("nodeUrl", proxyUrl.Url), utils.LogAttr("Method", parsing.GetApiName()), @@ -201,7 +212,7 @@ func (cf *ChainFetcher) Verify(ctx context.Context, verification VerificationCon if verification.LatestDistance != 0 && latestBlock != 0 && verification.ParseDirective.FunctionTag != spectypes.FUNCTION_TAG_GET_BLOCK_BY_NUM { parsedResultAsNumber := parsedInput.GetBlock() if parsedResultAsNumber == spectypes.NOT_APPLICABLE { - return utils.LavaFormatWarning("[-] verify failed to parse result as number", err, + return utils.LavaFormatWarning("[-] verify failed to parse result as number", nil, utils.LogAttr("chainId", chainId), utils.LogAttr("nodeUrl", proxyUrl.Url), utils.LogAttr("Method", parsing.GetApiName()), @@ -211,7 +222,7 @@ func (cf *ChainFetcher) Verify(ctx context.Context, verification VerificationCon } uint64ParsedResultAsNumber := uint64(parsedResultAsNumber) if uint64ParsedResultAsNumber > latestBlock { - return utils.LavaFormatWarning("[-] verify failed parsed result is greater than latestBlock", err, + return utils.LavaFormatWarning("[-] verify failed parsed result is greater than latestBlock", nil, utils.LogAttr("chainId", chainId), utils.LogAttr("nodeUrl", proxyUrl.Url), utils.LogAttr("Method", parsing.GetApiName()), @@ -220,7 +231,7 @@ func (cf *ChainFetcher) Verify(ctx context.Context, verification VerificationCon ) } if latestBlock-uint64ParsedResultAsNumber < verification.LatestDistance { - return utils.LavaFormatWarning("[-] verify failed expected block distance is not sufficient", err, + return utils.LavaFormatWarning("[-] verify failed expected block distance is not sufficient", nil, utils.LogAttr("chainId", chainId), utils.LogAttr("nodeUrl", proxyUrl.Url), utils.LogAttr("Method", parsing.GetApiName()), @@ -234,7 +245,7 @@ func (cf *ChainFetcher) Verify(ctx context.Context, verification VerificationCon if verification.Value != "*" && verification.Value != "" && verification.ParseDirective.FunctionTag != spectypes.FUNCTION_TAG_GET_BLOCK_BY_NUM { rawData := parsedInput.GetRawParsedData() if rawData != verification.Value { - return utils.LavaFormatWarning("[-] verify failed expected and received are different", err, + return utils.LavaFormatWarning("[-] verify failed expected and received are different", nil, utils.LogAttr("chainId", chainId), utils.LogAttr("nodeUrl", proxyUrl.Url), utils.LogAttr("rawParsedBlock", rawData), @@ -246,6 +257,7 @@ func (cf *ChainFetcher) Verify(ctx context.Context, verification VerificationCon ) } } + utils.LavaFormatInfo("[+] verified successfully", utils.LogAttr("chainId", chainId), utils.LogAttr("nodeUrl", proxyUrl.Url), diff --git a/protocol/chainlib/chainproxy/common.go b/protocol/chainlib/chainproxy/common.go index d0ddf9928e..ba02bc8b1c 100644 --- a/protocol/chainlib/chainproxy/common.go +++ b/protocol/chainlib/chainproxy/common.go @@ -5,6 +5,7 @@ import ( "github.com/goccy/go-json" + "github.com/lavanet/lava/v4/protocol/chainlib/chainproxy/rpcclient" "github.com/lavanet/lava/v4/protocol/parser" pairingtypes "github.com/lavanet/lava/v4/x/pairing/types" spectypes "github.com/lavanet/lava/v4/x/spec/types" @@ -93,6 +94,10 @@ func (dri DefaultRPCInput) GetID() json.RawMessage { return nil } +func (dri DefaultRPCInput) GetError() *rpcclient.JsonError { + return nil +} + func (dri DefaultRPCInput) ParseBlock(inp string) (int64, error) { return parser.ParseDefaultBlockParameter(inp) } diff --git a/protocol/chainlib/chainproxy/rpcInterfaceMessages/common.go b/protocol/chainlib/chainproxy/rpcInterfaceMessages/common.go index fb288637a2..0fc5974272 100644 --- a/protocol/chainlib/chainproxy/rpcInterfaceMessages/common.go +++ b/protocol/chainlib/chainproxy/rpcInterfaceMessages/common.go @@ -5,6 +5,7 @@ import ( "github.com/goccy/go-json" "github.com/lavanet/lava/v4/protocol/chainlib/chainproxy" + "github.com/lavanet/lava/v4/protocol/chainlib/chainproxy/rpcclient" "github.com/lavanet/lava/v4/protocol/parser" pairingtypes "github.com/lavanet/lava/v4/x/pairing/types" ) @@ -13,6 +14,7 @@ var WontCalculateBatchHash = sdkerrors.New("Wont calculate batch hash", 892, "wo type ParsableRPCInput struct { Result json.RawMessage + Error *rpcclient.JsonError chainproxy.BaseMessage } @@ -36,6 +38,10 @@ func (pri ParsableRPCInput) GetID() json.RawMessage { return nil } +func (pri ParsableRPCInput) GetError() *rpcclient.JsonError { + return pri.Error +} + type GenericMessage interface { GetHeaders() []pairingtypes.Metadata DisableErrorHandling() diff --git a/protocol/chainlib/chainproxy/rpcInterfaceMessages/grpcMessage.go b/protocol/chainlib/chainproxy/rpcInterfaceMessages/grpcMessage.go index 9734f73327..0bdaa8173a 100644 --- a/protocol/chainlib/chainproxy/rpcInterfaceMessages/grpcMessage.go +++ b/protocol/chainlib/chainproxy/rpcInterfaceMessages/grpcMessage.go @@ -126,6 +126,10 @@ func (gm GrpcMessage) GetID() json.RawMessage { return nil } +func (gm GrpcMessage) GetError() *rpcclient.JsonError { + return nil +} + func (gm GrpcMessage) NewParsableRPCInput(input json.RawMessage) (parser.RPCInput, error) { msgFactory := dynamic.NewMessageFactoryWithDefaults() if gm.methodDesc == nil { diff --git a/protocol/chainlib/chainproxy/rpcInterfaceMessages/jsonRPCMessage.go b/protocol/chainlib/chainproxy/rpcInterfaceMessages/jsonRPCMessage.go index cc31752d3c..59148df36d 100644 --- a/protocol/chainlib/chainproxy/rpcInterfaceMessages/jsonRPCMessage.go +++ b/protocol/chainlib/chainproxy/rpcInterfaceMessages/jsonRPCMessage.go @@ -121,11 +121,7 @@ func (jm JsonrpcMessage) NewParsableRPCInput(input json.RawMessage) (parser.RPCI return nil, utils.LavaFormatError("failed unmarshaling JsonrpcMessage", err, utils.Attribute{Key: "input", Value: input}) } - // Make sure the response does not have an error - if msg.Error != nil && msg.Result == nil { - return nil, utils.LavaFormatError("response is an error message", msg.Error) - } - return ParsableRPCInput{Result: msg.Result}, nil + return ParsableRPCInput{Result: msg.Result, Error: msg.Error}, nil } func (jm JsonrpcMessage) GetParams() interface{} { @@ -147,6 +143,10 @@ func (jm JsonrpcMessage) GetID() json.RawMessage { return jm.ID } +func (jm JsonrpcMessage) GetError() *rpcclient.JsonError { + return jm.Error +} + func (jm JsonrpcMessage) ParseBlock(inp string) (int64, error) { return parser.ParseDefaultBlockParameter(inp) } diff --git a/protocol/chainlib/chainproxy/rpcInterfaceMessages/restMessage.go b/protocol/chainlib/chainproxy/rpcInterfaceMessages/restMessage.go index 1d5c18329a..8d3da122fd 100644 --- a/protocol/chainlib/chainproxy/rpcInterfaceMessages/restMessage.go +++ b/protocol/chainlib/chainproxy/rpcInterfaceMessages/restMessage.go @@ -105,6 +105,10 @@ func (rm RestMessage) GetID() json.RawMessage { return nil } +func (rm RestMessage) GetError() *rpcclient.JsonError { + return nil +} + // ParseBlock parses default block number from string to int func (rm RestMessage) ParseBlock(inp string) (int64, error) { return parser.ParseDefaultBlockParameter(inp) diff --git a/protocol/chainlib/chainproxy/rpcclient/json.go b/protocol/chainlib/chainproxy/rpcclient/json.go index d2c1c5d777..794ad3ebe4 100755 --- a/protocol/chainlib/chainproxy/rpcclient/json.go +++ b/protocol/chainlib/chainproxy/rpcclient/json.go @@ -174,6 +174,20 @@ func (err *JsonError) ErrorData() interface{} { return err.Data } +func (err *JsonError) ToMap() map[string]interface{} { + if err == nil { + return nil + } + + return map[string]interface{}{ + "code": err.Code, + "message": err.Message, + "data": err.Data, + "name": err.Name, + "cause": err.Cause, + } +} + // Conn is a subset of the methods of net.Conn which are sufficient for ServerCodec. type Conn interface { io.ReadWriteCloser diff --git a/protocol/parser/parser.go b/protocol/parser/parser.go index e5ddf24e7c..750e84d6a4 100644 --- a/protocol/parser/parser.go +++ b/protocol/parser/parser.go @@ -11,6 +11,7 @@ import ( "github.com/itchyny/gojq" sdkerrors "cosmossdk.io/errors" + "github.com/lavanet/lava/v4/protocol/chainlib/chainproxy/rpcclient" "github.com/lavanet/lava/v4/utils" pairingtypes "github.com/lavanet/lava/v4/x/pairing/types" spectypes "github.com/lavanet/lava/v4/x/spec/types" @@ -28,6 +29,7 @@ var ValueNotSetError = sdkerrors.New("Value Not Set ", 6662, "when trying to par type RPCInput interface { GetParams() interface{} GetResult() json.RawMessage + GetError() *rpcclient.JsonError ParseBlock(block string) (int64, error) GetHeaders() []pairingtypes.Metadata GetMethod() string @@ -69,59 +71,24 @@ func ParseDefaultBlockParameter(block string) (int64, error) { return blockNum, nil } -func getParserTypeMap(parserType int) map[spectypes.PARSER_TYPE]struct{} { - switch parserType { - case PARSE_PARAMS: - return map[spectypes.PARSER_TYPE]struct{}{ - spectypes.PARSER_TYPE_BLOCK_LATEST: {}, - spectypes.PARSER_TYPE_DEFAULT_VALUE: {}, - spectypes.PARSER_TYPE_BLOCK_HASH: {}, - } - case PARSE_RESULT: - return map[spectypes.PARSER_TYPE]struct{}{ - spectypes.PARSER_TYPE_RESULT: {}, - } - default: - utils.LavaFormatError("missing parserType", nil, utils.LogAttr("parserType", parserType)) - return map[spectypes.PARSER_TYPE]struct{}{} - } -} - -func filterGenericParsersByType(genericParsers []spectypes.GenericParser, filterMap map[spectypes.PARSER_TYPE]struct{}) []spectypes.GenericParser { - retGenericParsers := []spectypes.GenericParser{} - for _, parser := range genericParsers { - if _, ok := filterMap[parser.ParseType]; ok { - retGenericParsers = append(retGenericParsers, parser) - } - } - return retGenericParsers -} - func parseInputWithGenericParsers(rpcInput RPCInput, genericParsers []spectypes.GenericParser) (*ParsedInput, bool) { managedToParseRawBlock := false if len(genericParsers) == 0 { return nil, managedToParseRawBlock } - genericParserResult, genericParserErr := ParseWithGenericParsers(rpcInput, filterGenericParsersByType(genericParsers, getParserTypeMap(PARSE_PARAMS))) + genericParserResult, genericParserErr := ParseWithGenericParsers(rpcInput, genericParsers) if genericParserErr != nil { return nil, managedToParseRawBlock } - parsed := NewParsedInput() - rawParsedData := genericParserResult.GetRawParsedData() - if rawParsedData != "" { - managedToParseRawBlock = true - parsed.parsedDataRaw = rawParsedData - } - - parsedBlockHashes, err := genericParserResult.GetBlockHashes() - if err == nil { + _, err := genericParserResult.GetBlockHashes() + if genericParserResult.GetParserError() == "" && (err == nil || genericParserResult.GetRawParsedData() != "") { + // if we got here, there is no parser error and we successfully parsed either the block hashes or the raw parsed data managedToParseRawBlock = true - parsed.parsedHashes = parsedBlockHashes } - return parsed, managedToParseRawBlock + return genericParserResult, managedToParseRawBlock } // ParseRawBlock attempts to parse a block from rpcInput and store it in parsedInput. @@ -158,8 +125,12 @@ func ParseRawBlock(rpcInput RPCInput, parsedInput *ParsedInput, defaultValue str parsedInput.SetBlock(parsedBlock) } -func parseInputWithLegacyBlockParser(rpcInput RPCInput, blockParser spectypes.BlockParser, source int) (string, bool, error) { - result, usedDefaultValue, err := legacyParse(rpcInput, blockParser, source) +func parseInputWithLegacyBlockParser(rpcInput RPCInput, blockParser spectypes.BlockParser, dataSource int) (string, bool, error) { + if rpcInput.GetError() != nil { + return "", false, utils.LavaFormatError("blockParsing - rpcInput is error", nil, utils.LogAttr("rpcInput.GetError()", rpcInput.GetError())) + } + + result, usedDefaultValue, err := legacyParse(rpcInput, blockParser, dataSource) if err != nil || result == nil { return "", usedDefaultValue, utils.LavaFormatDebug("blockParsing - parse failed", utils.LogAttr("error", err), @@ -186,11 +157,11 @@ func parseInputWithLegacyBlockParser(rpcInput RPCInput, blockParser spectypes.Bl // - 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. +// - dataSource: 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 { +func parseBlock(rpcInput RPCInput, blockParser spectypes.BlockParser, genericParsers []spectypes.GenericParser, dataSource int) *ParsedInput { parsedBlockInfo, _ := parseInputWithGenericParsers(rpcInput, genericParsers) if parsedBlockInfo == nil { parsedBlockInfo = NewParsedInput() @@ -202,7 +173,7 @@ func parseBlock(rpcInput RPCInput, blockParser spectypes.BlockParser, genericPar } } - parsedRawBlock, usedDefaultValue, err := parseInputWithLegacyBlockParser(rpcInput, blockParser, source) + parsedRawBlock, usedDefaultValue, err := parseInputWithLegacyBlockParser(rpcInput, blockParser, dataSource) if err == nil { parsedBlockInfo.UsedDefaultValue = usedDefaultValue } @@ -296,6 +267,7 @@ type ParsedInput struct { parsedDataRaw string parsedBlock int64 parsedHashes []string + parserError string UsedDefaultValue bool } @@ -321,6 +293,10 @@ func (p *ParsedInput) GetBlock() int64 { return p.parsedBlock } +func (p *ParsedInput) GetParserError() string { + return p.parserError +} + func (p *ParsedInput) GetBlockHashes() ([]string, error) { if len(p.parsedHashes) == 0 { return nil, fmt.Errorf("no parsed hashes found") @@ -334,7 +310,8 @@ func getMapForParse(rpcInput RPCInput) map[string]interface{} { if rpcInputResult != nil { json.Unmarshal(rpcInputResult, &result) } - return map[string]interface{}{"params": rpcInput.GetParams(), "result": result} + + return map[string]interface{}{"params": rpcInput.GetParams(), "result": result, "error": rpcInput.GetError().ToMap()} } func ParseWithGenericParsers(rpcInput RPCInput, genericParsers []spectypes.GenericParser) (*ParsedInput, error) { @@ -406,6 +383,7 @@ func parseGeneric(input interface{}, genericParser spectypes.GenericParser) (*Pa if !parseRule(genericParser.Rule, value) { return nil, utils.LavaFormatWarning("PARSER_TYPE_DEFAULT_VALUE Did not match any rule", nil, utils.LogAttr("value", value), utils.LogAttr("rules", genericParser.Rule)) } + utils.LavaFormatTrace("parsed generic value", utils.LogAttr("input", input), utils.LogAttr("genericParser", genericParser), @@ -422,10 +400,17 @@ func parseGeneric(input interface{}, genericParser spectypes.GenericParser) (*Pa 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_RESULT: + parsed := NewParsedInput() + strValue := blockInterfaceToString(value) + parsed.parsedDataRaw = strValue + if genericParser.Value != "*" && strValue != genericParser.Value { + parsed.parserError = fmt.Sprintf("expected %s, received %s", genericParser.Value, strValue) + } + return parsed, nil case spectypes.PARSER_TYPE_BLOCK_LATEST: parsed := NewParsedInput() - block := blockInterfaceToString(value) - parsed.parsedDataRaw = block + parsed.parsedDataRaw = blockInterfaceToString(value) 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 dbb06f2ddb..82d78c261f 100644 --- a/protocol/parser/parser_test.go +++ b/protocol/parser/parser_test.go @@ -6,6 +6,7 @@ import ( "reflect" "testing" + "github.com/lavanet/lava/v4/protocol/chainlib/chainproxy/rpcclient" pairingtypes "github.com/lavanet/lava/v4/x/pairing/types" spectypes "github.com/lavanet/lava/v4/x/spec/types" "github.com/stretchr/testify/require" @@ -35,6 +36,10 @@ func (rpcInputTest *RPCInputTest) GetID() json.RawMessage { return nil } +func (rpcInputTest *RPCInputTest) GetError() *rpcclient.JsonError { + return nil +} + func (rpcInputTest *RPCInputTest) ParseBlock(block string) (int64, error) { if rpcInputTest.ParseBlockFunc == nil { return ParseDefaultBlockParameter(block)