Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add an gRPC endpoint for request verification #45

Merged
merged 17 commits into from
May 6, 2021
Merged
4 changes: 0 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,6 @@ github.com/blevesearch/zap/v14 v14.0.0/go.mod h1:sUc/gPGJlFbSQ2ZUh/wGRYwkKx+Dg/5
github.com/btcsuite/btcd v0.0.0-20190115013929-ed77733ec07d/go.mod h1:d3C0AkH6BRcvO8T0UEPu53cnw4IbV63x1bEjildYhO0=
github.com/btcsuite/btcd v0.0.0-20190213025234-306aecffea32/go.mod h1:DrZx5ec/dmnfpw9KyYoQyYo7d0KEvTkk/5M/vbZjAr8=
github.com/btcsuite/btcd v0.0.0-20190523000118-16327141da8c/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI=
github.com/btcsuite/btcd v0.20.1-beta h1:Ik4hyJqN8Jfyv3S4AGBOmyouMsYE3EdYODkMbQjwPGw=
github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
github.com/btcsuite/btcd v0.21.0-beta h1:At9hIZdJW0s9E/fAz28nrz6AmcNlSVucCH796ZteX1M=
github.com/btcsuite/btcd v0.21.0-beta/go.mod h1:ZSWyehm27aAuS9bvkATT+Xte3hjHZ+MRgMY/8NJ7K94=
Expand Down Expand Up @@ -123,7 +122,6 @@ github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfc
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cosmos/cosmos-sdk v0.42.2 h1:t2jIxV5DGN1ynOwuSIvQUUHr7tAePN1AG5ArM7o8qos=
github.com/cosmos/cosmos-sdk v0.42.2/go.mod h1:xiLp1G8mumj82S5KLJGCAyeAlD+7VNomg/aRSJV12yk=
github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d h1:49RLWk1j44Xu4fjHb6JFYmeUnDORVwHNkDxaQ0ctCVU=
github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d/go.mod h1:tSxLoYXyBmiFeKpvmq4dzayMdCjCnu8uqmCysIGBT2Y=
github.com/cosmos/go-bip39 v1.0.0 h1:pcomnQdrdH22njcAatO0yWojsUnCO3y2tNoV1cb6hHY=
github.com/cosmos/go-bip39 v1.0.0/go.mod h1:RNJv0H/pOIVgxw6KS7QeX2a0Uo0aKUlfhZ4xuwvCdJw=
Expand Down Expand Up @@ -740,7 +738,6 @@ github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
github.com/sasha-s/go-deadlock v0.2.0 h1:lMqc+fUb7RrFS3gQLtoQsJ7/6TV/pAIFvBsqX73DK8Y=
github.com/sasha-s/go-deadlock v0.2.0/go.mod h1:StQn567HiB1fF2yJ44N9au7wOhrPS3iZqiDbRupzT10=
github.com/sasha-s/go-deadlock v0.2.1-0.20190427202633-1595213edefa h1:0U2s5loxrTy6/VgfVoLuVLFJcURKLH49ie0zSch7gh4=
github.com/sasha-s/go-deadlock v0.2.1-0.20190427202633-1595213edefa/go.mod h1:F73l+cr82YSh10GxyRI6qZiCgK64VaZjwesgfQ1/iLM=
Expand Down Expand Up @@ -830,7 +827,6 @@ github.com/tendermint/tendermint v0.34.8 h1:PMWgUx47FrNTsfhxCWzoiIlVAC1SE9+WBlns
github.com/tendermint/tendermint v0.34.8/go.mod h1:JVuu3V1ZexOaZG8VJMRl8lnfrGw6hEB2TVnoUwKRbss=
github.com/tendermint/tm-db v0.5.1/go.mod h1:g92zWjHpCYlEvQXvy9M168Su8V1IBEeawpXVVBaK4f4=
github.com/tendermint/tm-db v0.6.2/go.mod h1:GYtQ67SUvATOcoY8/+x6ylk8Qo02BQyLrAs+yAcLvGI=
github.com/tendermint/tm-db v0.6.3 h1:ZkhQcKnB8/2jr5EaZwGndN4owkPsGezW2fSisS9zGbg=
github.com/tendermint/tm-db v0.6.3/go.mod h1:lfA1dL9/Y/Y8wwyPp2NMLyn5P5Ptr/gvDFNWtrCWSf8=
github.com/tendermint/tm-db v0.6.4 h1:3N2jlnYQkXNQclQwd/eKV/NzlqPlfK21cpRRIx80XXQ=
github.com/tendermint/tm-db v0.6.4/go.mod h1:dptYhIpJ2M5kUuenLr+Yyf3zQOv1SgBZcl8/BmWlMBw=
Expand Down
20 changes: 20 additions & 0 deletions proto/oracle/v1/oracle.proto
Original file line number Diff line number Diff line change
Expand Up @@ -276,3 +276,23 @@ message IBCSource {
// SourcePort
string source_port = 2;
}

// RequestVerification is a message that is constructed and signed by a reporter
// to be used as a part of verification of oracle request.
message RequestVerification {
option (gogoproto.equal) = true;
// ChainID is the ID of targeted chain
string chain_id = 1 [ (gogoproto.customname) = "ChainID" ];
// Validator is an validator address
string validator = 2;
// RequestID is the targeted request ID
int64 request_id = 3 [
(gogoproto.customname) = "RequestID",
(gogoproto.casttype) = "RequestID"
];
// ExternalID is the oracle's external ID of data source
int64 external_id = 4 [
(gogoproto.customname) = "ExternalID",
(gogoproto.casttype) = "ExternalID"
];
}
37 changes: 37 additions & 0 deletions proto/oracle/v1/query.proto
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,13 @@ service Query {
returns (QueryRequestPriceResponse) {
option (google.api.http).post = "/oracle/request_prices";
}

// RequestVerification verifies a request to make sure that
// all information that will be used to report the data is valid
rpc RequestVerification(QueryRequestVerificationRequest)
returns (QueryRequestVerificationResponse) {
option (google.api.http).post = "/oracle/v1/verify_request";
}
}

// QueryCountsRequest is request type for the Query/Count RPC method.
Expand Down Expand Up @@ -170,3 +177,33 @@ message QueryRequestPriceResponse {
int64 ask_count = 3;
int64 min_count = 4;
}

// QueryRequestVerificationRequest is request type for the Query/RequestVerification RPC
message QueryRequestVerificationRequest {
// ChainID is the chain ID to identify which chain ID is used for the verification
string chain_id = 1;
// Validator is a validator address
string validator = 2;
// RequestID is oracle request ID
int64 request_id = 3;
// ExternalID is an oracle's external ID
int64 external_id = 4;
// Reporter is an bech32-encoded public key of the reporter authorized by the validator
string reporter = 5;
// Signature is a signature signed by the reporter using reporter's private key
bytes signature = 6;
}

// QueryRequestVerificationResponse is response type for the Query/RequestVerification RPC
message QueryRequestVerificationResponse {
// ChainID is the targeted chain ID
string chain_id = 1;
// Validator is the targeted validator address
string validator = 2;
// RequestID is the ID of targeted request
int64 request_id = 3;
// ExternalID is the ID of targeted oracle's external data source
int64 external_id = 4;
// DataSourceID is the ID of a data source that relates to the targeted external ID
int64 data_source_id = 5;
}
51 changes: 49 additions & 2 deletions x/oracle/client/cli/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import (
// "net/http"

"context"
"encoding/hex"
"fmt"
"strconv"

"github.com/cosmos/cosmos-sdk/client"
Expand Down Expand Up @@ -37,7 +39,8 @@ func GetQueryCmd() *cobra.Command {
// GetQueryCmdValidatorStatus(),
GetQueryCmdReporters(),
GetQueryActiveValidators(),
// GetQueryPendingRequests(storeKey, cdc),
// GetQueryPendingRequests(storeKey, cdc),
GetQueryRequestVerification(),
)
return oracleCmd
}
Expand Down Expand Up @@ -223,7 +226,7 @@ func GetQueryCmdReporters() *cobra.Command {
return err
}
queryClient := types.NewQueryClient(clientCtx)
r, err := queryClient.Reporters(context.Background(), &types.QueryReportersRequest{ValidatorAddress: args[1]})
r, err := queryClient.Reporters(context.Background(), &types.QueryReportersRequest{ValidatorAddress: args[0]})
if err != nil {
return err
}
Expand Down Expand Up @@ -282,3 +285,47 @@ func GetQueryActiveValidators() *cobra.Command {
// },
// }
// }

func GetQueryRequestVerification() *cobra.Command {
cmd := &cobra.Command{
Use: "verify-request [chain-id] [validator-addr] [request-id] [data-source-external-id] [reporter-addr] [reporter-signature-hex]",
ntchjb marked this conversation as resolved.
Show resolved Hide resolved
Args: cobra.ExactArgs(6),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx, err := client.GetClientQueryContext(cmd)
if err != nil {
return err
}
queryClient := types.NewQueryClient(clientCtx)
requestID, err := strconv.ParseInt(args[2], 10, 64)
if err != nil {
return fmt.Errorf("unable to parse request ID: %w", err)
}
externalID, err := strconv.ParseInt(args[3], 10, 64)
if err != nil {
return fmt.Errorf("unable to parse external ID: %w", err)
}

signature, err := hex.DecodeString(args[5])
if err != nil {
return fmt.Errorf("unable to parse signature: %w", err)
}

r, err := queryClient.RequestVerification(context.Background(), &types.QueryRequestVerificationRequest{
ChainId: args[0],
Validator: args[1],
RequestId: requestID,
ExternalId: externalID,
Reporter: args[4],
Signature: signature,
})
if err != nil {
return err
}

return clientCtx.PrintProto(r)
},
}
flags.AddQueryFlagsToCmd(cmd)

return cmd
}
105 changes: 105 additions & 0 deletions x/oracle/keeper/grpc_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package keeper

import (
"context"
"fmt"

"github.com/bandprotocol/chain/x/oracle/types"
sdk "github.com/cosmos/cosmos-sdk/types"
Expand Down Expand Up @@ -155,3 +156,107 @@ func (k Querier) RequestSearch(c context.Context, req *types.QueryRequestSearchR
func (k Querier) RequestPrice(c context.Context, req *types.QueryRequestPriceRequest) (*types.QueryRequestPriceResponse, error) {
return &types.QueryRequestPriceResponse{}, nil
}

func (k Querier) RequestVerification(c context.Context, req *types.QueryRequestVerificationRequest) (*types.QueryRequestVerificationResponse, error) {
// Request should not be empty
if req == nil {
return nil, status.Error(codes.InvalidArgument, "empty request")
}

ctx := sdk.UnwrapSDKContext(c)

// Provided chain ID should match current chain ID
if ctx.ChainID() != req.ChainId {
return nil, status.Error(codes.Internal, fmt.Sprintf("provided chain ID does not match the validator's chain ID; expected %s, got %s", ctx.ChainID(), req.ChainId))
}

// Provided validator's address should be valid
validator, err := sdk.ValAddressFromBech32(req.Validator)
if err != nil {
return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("unable to parse validator address: %s", err.Error()))
}

// Provided signature should be valid, which means this query request should be signed by the provided reporter
reporterPubKey, err := sdk.GetPubKeyFromBech32(sdk.Bech32PubKeyTypeAccPub, req.Reporter)
if err != nil {
return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("unable to get reporter's public key: %s", err.Error()))
}
requestVerificationContent := types.NewRequestVerification(req.ChainId, validator, types.RequestID(req.RequestId), types.ExternalID(req.ExternalId))
signByte := requestVerificationContent.GetSignBytes()
if !reporterPubKey.VerifySignature(signByte, req.Signature) {
return nil, status.Error(codes.InvalidArgument, "invalid reporter's signature")
ntchjb marked this conversation as resolved.
Show resolved Hide resolved
}

// Provided reporter should be authorized by the provided validator
reporters := k.GetReporters(ctx, validator)
reporter := sdk.AccAddress(reporterPubKey.Address().Bytes())
isReporterAuthorizedByValidator := false
for _, existingReporter := range reporters {
if reporter.Equals(existingReporter) {
isReporterAuthorizedByValidator = true
break
}
}
if !isReporterAuthorizedByValidator {
return nil, status.Error(codes.Unauthenticated, fmt.Sprintf("%s is not an authorized reporter of %s", reporter, req.Validator))
ntchjb marked this conversation as resolved.
Show resolved Hide resolved
}

// Provided request should exist on chain
request, err := k.GetRequest(ctx, types.RequestID(req.RequestId))
if err != nil {
return nil, status.Error(codes.NotFound, fmt.Sprintf("unable to get request from chain: %s", err.Error()))
}

// Provided validator should be assigned to response to the request
isValidatorAssigned := false
for _, requestedValidator := range request.RequestedValidators {
v, _ := sdk.ValAddressFromBech32(requestedValidator)
if validator.Equals(v) {
isValidatorAssigned = true
break
}
}
if !isValidatorAssigned {
return nil, status.Error(codes.Unauthenticated, fmt.Sprintf("%s is not assigned for request ID %d", validator, req.RequestId))
ntchjb marked this conversation as resolved.
Show resolved Hide resolved
}

// Provided external ID should be required by the request determined by oracle script
var dataSourceID *types.DataSourceID
for _, rawRequest := range request.RawRequests {
if rawRequest.ExternalID == types.ExternalID(req.ExternalId) {
dataSourceID = &rawRequest.DataSourceID
break
}
}
if dataSourceID == nil {
return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("no data source required by the request %d found which relates to the external data source with ID %d.", req.RequestId, req.ExternalId))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it should return 502 (Internal server error). If a request existed it should have a data source for every raw requests

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there is another possibility that a client sends some random external ID that does not exists in the request, which causes data source ID to be nil.

}

// Provided validator should not have reported data for the request
reports := k.GetReports(ctx, types.RequestID(req.RequestId))
isValidatorReported := false
for _, report := range reports {
reportVal, _ := sdk.ValAddressFromBech32(report.Validator)
if reportVal.Equals(validator) {
isValidatorReported = true
break
}
}
if isValidatorReported {
return nil, status.Error(codes.AlreadyExists, fmt.Sprintf("validator %s already submitted data report for this request", validator))
}

// The request should not be expired
params := k.GetParams(ctx)
ntchjb marked this conversation as resolved.
Show resolved Hide resolved
if request.RequestHeight+int64(params.ExpirationBlockCount) < ctx.BlockHeader().Height {
return nil, status.Error(codes.DeadlineExceeded, fmt.Sprintf("Request with ID %d is already expired", req.RequestId))
}

return &types.QueryRequestVerificationResponse{
ChainId: req.ChainId,
Validator: req.Validator,
RequestId: req.RequestId,
ExternalId: req.ExternalId,
DataSourceId: int64(*dataSourceID),
}, nil
}
Loading