diff --git a/go.mod b/go.mod index 98831c8..a2cf2c5 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,6 @@ require ( golang.org/x/text v0.3.6 google.golang.org/genproto v0.0.0-20210524171403-669157292da3 // indirect google.golang.org/grpc v1.38.0 - google.golang.org/protobuf v1.26.0 + google.golang.org/protobuf v1.27.1 gopkg.in/karalabe/cookiejar.v1 v1.0.0-20141109175019-e1490cae028c ) diff --git a/go.sum b/go.sum index 980e6d7..68e7e17 100644 --- a/go.sum +++ b/go.sum @@ -313,8 +313,9 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/main.go b/main.go index e7daab8..242e726 100644 --- a/main.go +++ b/main.go @@ -5,11 +5,8 @@ import ( "fmt" "log" "net" - "os" - "strings" "time" - "github.com/akamensky/argparse" pb "github.com/lbryio/hub/protobuf/go" "github.com/lbryio/hub/server" "github.com/lbryio/lbry.go/v2/extras/util" @@ -17,176 +14,36 @@ import ( "google.golang.org/grpc/reflection" ) -const ( - defaultHost = "0.0.0.0" - defaultPort = "50051" - defaultEsHost = "http://localhost" - defaultEsIndex = "claims" - defaultEsPort = "9200" - defaultRefreshDelta = 5 - defaultCacheTTL = 5 -) - -func GetEnvironment(data []string, getkeyval func(item string) (key, val string)) map[string]string { - items := make(map[string]string) - for _, item := range data { - key, val := getkeyval(item) - items[key] = val - } - return items -} - -func GetEnvironmentStandard() map[string]string { - return GetEnvironment(os.Environ(), func(item string) (key, val string) { - splits := strings.Split(item, "=") - key = splits[0] - val = splits[1] - return - }) -} - -/* -func makeServeCmd(parser *argparse.Parser) *argparse.Command { - serveCmd := parser.NewCommand("serve", "start the hub server") - - host := serveCmd.String("", "rpchost", &argparse.Options{Required: false, Help: "host", Default: defaultHost}) - port := serveCmd.String("", "rpcport", &argparse.Options{Required: false, Help: "port", Default: defaultPort}) - esHost := serveCmd.String("", "eshost", &argparse.Options{Required: false, Help: "host", Default: defaultEsHost}) - esPort := serveCmd.String("", "esport", &argparse.Options{Required: false, Help: "port", Default: defaultEsPort}) - dev := serveCmd.Flag("", "dev", &argparse.Options{Required: false, Help: "port", Default: false}) - - return serveCmd -} -*/ - -func parseArgs(searchRequest *pb.SearchRequest) *server.Args { - - environment := GetEnvironmentStandard() - parser := argparse.NewParser("hub", "hub server and client") - - serveCmd := parser.NewCommand("serve", "start the hub server") - searchCmd := parser.NewCommand("search", "claim search") - debug := parser.Flag("", "debug", &argparse.Options{Required: false, Help: "enable debug logging", Default: false}) - - host := parser.String("", "rpchost", &argparse.Options{Required: false, Help: "RPC host", Default: defaultHost}) - port := parser.String("", "rpcport", &argparse.Options{Required: false, Help: "RPC port", Default: defaultPort}) - esHost := parser.String("", "eshost", &argparse.Options{Required: false, Help: "elasticsearch host", Default: defaultEsHost}) - esPort := parser.String("", "esport", &argparse.Options{Required: false, Help: "elasticsearch port", Default: defaultEsPort}) - esIndex := parser.String("", "esindex", &argparse.Options{Required: false, Help: "elasticsearch index name", Default: defaultEsIndex}) - refreshDelta := parser.Int("", "refresh-delta", &argparse.Options{Required: false, Help: "elasticsearch index refresh delta in seconds", Default: defaultRefreshDelta}) - cacheTTL := parser.Int("", "cachettl", &argparse.Options{Required: false, Help: "Cache TTL in minutes", Default: defaultCacheTTL}) - - text := parser.String("", "text", &argparse.Options{Required: false, Help: "text query"}) - name := parser.String("", "name", &argparse.Options{Required: false, Help: "name"}) - claimType := parser.String("", "claim_type", &argparse.Options{Required: false, Help: "claim_type"}) - id := parser.String("", "id", &argparse.Options{Required: false, Help: "id"}) - author := parser.String("", "author", &argparse.Options{Required: false, Help: "author"}) - title := parser.String("", "title", &argparse.Options{Required: false, Help: "title"}) - description := parser.String("", "description", &argparse.Options{Required: false, Help: "description"}) - channelId := parser.String("", "channel_id", &argparse.Options{Required: false, Help: "channel id"}) - channelIds := parser.StringList("", "channel_ids", &argparse.Options{Required: false, Help: "channel ids"}) - - // Now parse the arguments - err := parser.Parse(os.Args) - if err != nil { - log.Fatalln(parser.Usage(err)) - } - - args := &server.Args{ - CmdType: server.SearchCmd, - Host: *host, - Port: ":" + *port, - EsHost: *esHost, - EsPort: *esPort, - EsIndex: *esIndex, - Debug: *debug, - RefreshDelta: *refreshDelta, - CacheTTL: *cacheTTL, - } - - if esHost, ok := environment["ELASTIC_HOST"]; ok { - args.EsHost = esHost - } - - if !strings.HasPrefix(args.EsHost, "http") { - args.EsHost = "http://" + args.EsHost - } - - if esPort, ok := environment["ELASTIC_PORT"]; ok { - args.EsPort = esPort - } - - /* - Verify no invalid argument combinations - */ - if len(*channelIds) > 0 && *channelId != "" { - log.Fatal("Cannot specify both channel_id and channel_ids") - } - - if serveCmd.Happened() { - args.CmdType = server.ServeCmd - } else if searchCmd.Happened() { - args.CmdType = server.SearchCmd - } - - if *text != "" { - searchRequest.Text = *text - } - if *name != "" { - searchRequest.ClaimName = *name - } - if *claimType != "" { - searchRequest.ClaimType = []string{*claimType} - } - if *id != "" { - searchRequest.ClaimId = &pb.InvertibleField{Invert: false, Value: []string{*id}} - } - if *author != "" { - searchRequest.Author = *author - } - if *title != "" { - searchRequest.Title = *title - } - if *description != "" { - searchRequest.Description = *description - } - if *channelId != "" { - searchRequest.ChannelId = &pb.InvertibleField{Invert: false, Value: []string{*channelId}} - } - if len(*channelIds) > 0 { - searchRequest.ChannelId = &pb.InvertibleField{Invert: false, Value: *channelIds} - } - - return args -} - func main() { + ctx := context.Background() searchRequest := &pb.SearchRequest{} - args := parseArgs(searchRequest) + args := server.ParseArgs(searchRequest) if args.CmdType == server.ServeCmd { + // This will cancel goroutines with the server finishes. + ctxWCancel, cancel := context.WithCancel(ctx) + defer cancel() - l, err := net.Listen("tcp", args.Port) + l, err := net.Listen("tcp", ":"+args.Port) if err != nil { log.Fatalf("failed to listen: %v", err) } - s := server.MakeHubServer(args) + s := server.MakeHubServer(ctxWCancel, args) pb.RegisterHubServer(s.GrpcServer, s) reflection.Register(s.GrpcServer) log.Printf("listening on %s\n", l.Addr().String()) log.Println(s.Args) - go s.PromethusEndpoint("2112", "metrics") if err := s.GrpcServer.Serve(l); err != nil { log.Fatalf("failed to serve: %v", err) } return } - conn, err := grpc.Dial("localhost"+args.Port, + conn, err := grpc.Dial("localhost:"+args.Port, grpc.WithInsecure(), grpc.WithBlock(), ) @@ -197,13 +54,13 @@ func main() { c := pb.NewHubClient(conn) - ctx, cancel := context.WithTimeout(context.Background(), time.Second) - defer cancel() + ctxWTimeout, cancelQuery := context.WithTimeout(ctx, time.Second) + defer cancelQuery() log.Println(args) switch args.CmdType { case server.SearchCmd: - r, err := c.Search(ctx, searchRequest) + r, err := c.Search(ctxWTimeout, searchRequest) if err != nil { log.Fatal(err) } diff --git a/protobuf/definitions/hub.proto b/protobuf/definitions/hub.proto index 13d2213..ca13f6f 100644 --- a/protobuf/definitions/hub.proto +++ b/protobuf/definitions/hub.proto @@ -8,6 +8,10 @@ package pb; service Hub { rpc Search (SearchRequest) returns (Outputs) {} rpc Ping (EmptyMessage) returns (StringValue) {} + rpc Hello (HelloMessage) returns (HelloMessage) {} + rpc AddPeer (ServerMessage) returns (StringValue) {} + rpc PeerSubscribeStreaming (ServerMessage) returns (stream ServerMessage) {} + rpc PeerSubscribe (ServerMessage) returns (StringValue) {} rpc Version (EmptyMessage) returns (StringValue) {} rpc Features (EmptyMessage) returns (StringValue) {} rpc Broadcast(EmptyMessage) returns (UInt32Value) {} @@ -15,6 +19,17 @@ service Hub { message EmptyMessage {} +message ServerMessage { + string address = 1; + string port = 2; +} + +message HelloMessage { + string port = 1; + string host = 2; + repeated ServerMessage servers = 3; +} + message InvertibleField { bool invert = 1; repeated string value = 2; diff --git a/protobuf/go/hub.pb.go b/protobuf/go/hub.pb.go index 46c53fb..81f91cd 100644 --- a/protobuf/go/hub.pb.go +++ b/protobuf/go/hub.pb.go @@ -72,7 +72,7 @@ func (x RangeField_Op) Number() protoreflect.EnumNumber { // Deprecated: Use RangeField_Op.Descriptor instead. func (RangeField_Op) EnumDescriptor() ([]byte, []int) { - return file_hub_proto_rawDescGZIP(), []int{5, 0} + return file_hub_proto_rawDescGZIP(), []int{7, 0} } type EmptyMessage struct { @@ -113,6 +113,124 @@ func (*EmptyMessage) Descriptor() ([]byte, []int) { return file_hub_proto_rawDescGZIP(), []int{0} } +type ServerMessage struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address"` + Port string `protobuf:"bytes,2,opt,name=port,proto3" json:"port"` +} + +func (x *ServerMessage) Reset() { + *x = ServerMessage{} + if protoimpl.UnsafeEnabled { + mi := &file_hub_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ServerMessage) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ServerMessage) ProtoMessage() {} + +func (x *ServerMessage) ProtoReflect() protoreflect.Message { + mi := &file_hub_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ServerMessage.ProtoReflect.Descriptor instead. +func (*ServerMessage) Descriptor() ([]byte, []int) { + return file_hub_proto_rawDescGZIP(), []int{1} +} + +func (x *ServerMessage) GetAddress() string { + if x != nil { + return x.Address + } + return "" +} + +func (x *ServerMessage) GetPort() string { + if x != nil { + return x.Port + } + return "" +} + +type HelloMessage struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Port string `protobuf:"bytes,1,opt,name=port,proto3" json:"port"` + Host string `protobuf:"bytes,2,opt,name=host,proto3" json:"host"` + Servers []*ServerMessage `protobuf:"bytes,3,rep,name=servers,proto3" json:"servers"` +} + +func (x *HelloMessage) Reset() { + *x = HelloMessage{} + if protoimpl.UnsafeEnabled { + mi := &file_hub_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *HelloMessage) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HelloMessage) ProtoMessage() {} + +func (x *HelloMessage) ProtoReflect() protoreflect.Message { + mi := &file_hub_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HelloMessage.ProtoReflect.Descriptor instead. +func (*HelloMessage) Descriptor() ([]byte, []int) { + return file_hub_proto_rawDescGZIP(), []int{2} +} + +func (x *HelloMessage) GetPort() string { + if x != nil { + return x.Port + } + return "" +} + +func (x *HelloMessage) GetHost() string { + if x != nil { + return x.Host + } + return "" +} + +func (x *HelloMessage) GetServers() []*ServerMessage { + if x != nil { + return x.Servers + } + return nil +} + type InvertibleField struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -125,7 +243,7 @@ type InvertibleField struct { func (x *InvertibleField) Reset() { *x = InvertibleField{} if protoimpl.UnsafeEnabled { - mi := &file_hub_proto_msgTypes[1] + mi := &file_hub_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -138,7 +256,7 @@ func (x *InvertibleField) String() string { func (*InvertibleField) ProtoMessage() {} func (x *InvertibleField) ProtoReflect() protoreflect.Message { - mi := &file_hub_proto_msgTypes[1] + mi := &file_hub_proto_msgTypes[3] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -151,7 +269,7 @@ func (x *InvertibleField) ProtoReflect() protoreflect.Message { // Deprecated: Use InvertibleField.ProtoReflect.Descriptor instead. func (*InvertibleField) Descriptor() ([]byte, []int) { - return file_hub_proto_rawDescGZIP(), []int{1} + return file_hub_proto_rawDescGZIP(), []int{3} } func (x *InvertibleField) GetInvert() bool { @@ -179,7 +297,7 @@ type StringValue struct { func (x *StringValue) Reset() { *x = StringValue{} if protoimpl.UnsafeEnabled { - mi := &file_hub_proto_msgTypes[2] + mi := &file_hub_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -192,7 +310,7 @@ func (x *StringValue) String() string { func (*StringValue) ProtoMessage() {} func (x *StringValue) ProtoReflect() protoreflect.Message { - mi := &file_hub_proto_msgTypes[2] + mi := &file_hub_proto_msgTypes[4] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -205,7 +323,7 @@ func (x *StringValue) ProtoReflect() protoreflect.Message { // Deprecated: Use StringValue.ProtoReflect.Descriptor instead. func (*StringValue) Descriptor() ([]byte, []int) { - return file_hub_proto_rawDescGZIP(), []int{2} + return file_hub_proto_rawDescGZIP(), []int{4} } func (x *StringValue) GetValue() string { @@ -226,7 +344,7 @@ type BoolValue struct { func (x *BoolValue) Reset() { *x = BoolValue{} if protoimpl.UnsafeEnabled { - mi := &file_hub_proto_msgTypes[3] + mi := &file_hub_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -239,7 +357,7 @@ func (x *BoolValue) String() string { func (*BoolValue) ProtoMessage() {} func (x *BoolValue) ProtoReflect() protoreflect.Message { - mi := &file_hub_proto_msgTypes[3] + mi := &file_hub_proto_msgTypes[5] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -252,7 +370,7 @@ func (x *BoolValue) ProtoReflect() protoreflect.Message { // Deprecated: Use BoolValue.ProtoReflect.Descriptor instead. func (*BoolValue) Descriptor() ([]byte, []int) { - return file_hub_proto_rawDescGZIP(), []int{3} + return file_hub_proto_rawDescGZIP(), []int{5} } func (x *BoolValue) GetValue() bool { @@ -273,7 +391,7 @@ type UInt32Value struct { func (x *UInt32Value) Reset() { *x = UInt32Value{} if protoimpl.UnsafeEnabled { - mi := &file_hub_proto_msgTypes[4] + mi := &file_hub_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -286,7 +404,7 @@ func (x *UInt32Value) String() string { func (*UInt32Value) ProtoMessage() {} func (x *UInt32Value) ProtoReflect() protoreflect.Message { - mi := &file_hub_proto_msgTypes[4] + mi := &file_hub_proto_msgTypes[6] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -299,7 +417,7 @@ func (x *UInt32Value) ProtoReflect() protoreflect.Message { // Deprecated: Use UInt32Value.ProtoReflect.Descriptor instead. func (*UInt32Value) Descriptor() ([]byte, []int) { - return file_hub_proto_rawDescGZIP(), []int{4} + return file_hub_proto_rawDescGZIP(), []int{6} } func (x *UInt32Value) GetValue() uint32 { @@ -321,7 +439,7 @@ type RangeField struct { func (x *RangeField) Reset() { *x = RangeField{} if protoimpl.UnsafeEnabled { - mi := &file_hub_proto_msgTypes[5] + mi := &file_hub_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -334,7 +452,7 @@ func (x *RangeField) String() string { func (*RangeField) ProtoMessage() {} func (x *RangeField) ProtoReflect() protoreflect.Message { - mi := &file_hub_proto_msgTypes[5] + mi := &file_hub_proto_msgTypes[7] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -347,7 +465,7 @@ func (x *RangeField) ProtoReflect() protoreflect.Message { // Deprecated: Use RangeField.ProtoReflect.Descriptor instead. func (*RangeField) Descriptor() ([]byte, []int) { - return file_hub_proto_rawDescGZIP(), []int{5} + return file_hub_proto_rawDescGZIP(), []int{7} } func (x *RangeField) GetOp() RangeField_Op { @@ -429,7 +547,7 @@ type SearchRequest struct { func (x *SearchRequest) Reset() { *x = SearchRequest{} if protoimpl.UnsafeEnabled { - mi := &file_hub_proto_msgTypes[6] + mi := &file_hub_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -442,7 +560,7 @@ func (x *SearchRequest) String() string { func (*SearchRequest) ProtoMessage() {} func (x *SearchRequest) ProtoReflect() protoreflect.Message { - mi := &file_hub_proto_msgTypes[6] + mi := &file_hub_proto_msgTypes[8] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -455,7 +573,7 @@ func (x *SearchRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SearchRequest.ProtoReflect.Descriptor instead. func (*SearchRequest) Descriptor() ([]byte, []int) { - return file_hub_proto_rawDescGZIP(), []int{6} + return file_hub_proto_rawDescGZIP(), []int{8} } func (x *SearchRequest) GetClaimId() *InvertibleField { @@ -848,186 +966,210 @@ var File_hub_proto protoreflect.FileDescriptor var file_hub_proto_rawDesc = []byte{ 0x0a, 0x09, 0x68, 0x75, 0x62, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x02, 0x70, 0x62, 0x1a, 0x0c, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x0e, 0x0a, - 0x0c, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x3f, 0x0a, - 0x0f, 0x49, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x69, 0x62, 0x6c, 0x65, 0x46, 0x69, 0x65, 0x6c, 0x64, - 0x12, 0x16, 0x0a, 0x06, 0x69, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x06, 0x69, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, - 0x65, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x23, - 0x0a, 0x0b, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x14, 0x0a, - 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x22, 0x21, 0x0a, 0x09, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, - 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x23, 0x0a, 0x0b, 0x55, 0x49, 0x6e, 0x74, 0x33, 0x32, - 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x75, 0x0a, 0x0a, 0x52, - 0x61, 0x6e, 0x67, 0x65, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x12, 0x21, 0x0a, 0x02, 0x6f, 0x70, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x11, 0x2e, 0x70, 0x62, 0x2e, 0x52, 0x61, 0x6e, 0x67, 0x65, - 0x46, 0x69, 0x65, 0x6c, 0x64, 0x2e, 0x4f, 0x70, 0x52, 0x02, 0x6f, 0x70, 0x12, 0x14, 0x0a, 0x05, + 0x0c, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x3d, 0x0a, + 0x0d, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x18, + 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6f, 0x72, 0x74, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x22, 0x63, 0x0a, 0x0c, + 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, + 0x70, 0x6f, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x6f, 0x72, 0x74, + 0x12, 0x12, 0x0a, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, + 0x68, 0x6f, 0x73, 0x74, 0x12, 0x2b, 0x0a, 0x07, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x18, + 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x70, 0x62, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, + 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x07, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, + 0x73, 0x22, 0x3f, 0x0a, 0x0f, 0x49, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x69, 0x62, 0x6c, 0x65, 0x46, + 0x69, 0x65, 0x6c, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x69, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x69, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x22, 0x2e, 0x0a, 0x02, 0x4f, 0x70, 0x12, 0x06, 0x0a, 0x02, 0x45, 0x51, 0x10, 0x00, - 0x12, 0x07, 0x0a, 0x03, 0x4c, 0x54, 0x45, 0x10, 0x01, 0x12, 0x07, 0x0a, 0x03, 0x47, 0x54, 0x45, - 0x10, 0x02, 0x12, 0x06, 0x0a, 0x02, 0x4c, 0x54, 0x10, 0x03, 0x12, 0x06, 0x0a, 0x02, 0x47, 0x54, - 0x10, 0x04, 0x22, 0xe0, 0x11, 0x0a, 0x0d, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x08, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x5f, 0x69, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x62, 0x2e, 0x49, 0x6e, 0x76, 0x65, - 0x72, 0x74, 0x69, 0x62, 0x6c, 0x65, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x52, 0x07, 0x63, 0x6c, 0x61, - 0x69, 0x6d, 0x49, 0x64, 0x12, 0x32, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, - 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x62, 0x2e, 0x49, 0x6e, - 0x76, 0x65, 0x72, 0x74, 0x69, 0x62, 0x6c, 0x65, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x52, 0x09, 0x63, - 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x65, 0x78, 0x74, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x65, 0x78, 0x74, 0x12, 0x14, 0x0a, 0x05, - 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6c, 0x69, 0x6d, - 0x69, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x62, 0x79, 0x18, 0x05, - 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x42, 0x79, 0x12, 0x16, 0x0a, - 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x6f, - 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x69, 0x73, 0x5f, 0x63, 0x6f, 0x6e, 0x74, - 0x72, 0x6f, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x69, - 0x73, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x12, 0x31, 0x0a, 0x15, - 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x74, 0x61, 0x6b, 0x65, 0x5f, 0x6f, 0x76, 0x65, 0x72, 0x5f, 0x68, - 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x6c, 0x61, 0x73, - 0x74, 0x54, 0x61, 0x6b, 0x65, 0x4f, 0x76, 0x65, 0x72, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, - 0x1d, 0x0a, 0x0a, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x09, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x27, - 0x0a, 0x0f, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x6e, 0x61, 0x6d, - 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x69, - 0x7a, 0x65, 0x64, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x2f, 0x0a, 0x0b, 0x74, 0x78, 0x5f, 0x70, 0x6f, - 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x70, - 0x62, 0x2e, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x52, 0x0a, 0x74, 0x78, - 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x26, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, - 0x6e, 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x70, 0x62, 0x2e, 0x52, 0x61, - 0x6e, 0x67, 0x65, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, - 0x12, 0x2c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x0d, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x70, 0x62, 0x2e, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x46, 0x69, - 0x65, 0x6c, 0x64, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x3d, - 0x0a, 0x12, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x73, - 0x74, 0x61, 0x6d, 0x70, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x70, 0x62, 0x2e, - 0x52, 0x61, 0x6e, 0x67, 0x65, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x52, 0x11, 0x63, 0x72, 0x65, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x26, 0x0a, - 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, - 0x70, 0x62, 0x2e, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x52, 0x06, 0x68, - 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x37, 0x0a, 0x0f, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, - 0x2e, 0x70, 0x62, 0x2e, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x52, 0x0e, - 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x3b, - 0x0a, 0x11, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x68, 0x65, 0x69, - 0x67, 0x68, 0x74, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x70, 0x62, 0x2e, 0x52, - 0x61, 0x6e, 0x67, 0x65, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x52, 0x10, 0x61, 0x63, 0x74, 0x69, 0x76, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x3b, 0x0a, 0x11, 0x65, - 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, - 0x18, 0x12, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x70, 0x62, 0x2e, 0x52, 0x61, 0x6e, 0x67, - 0x65, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x52, 0x10, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x31, 0x0a, 0x0c, 0x72, 0x65, 0x6c, 0x65, - 0x61, 0x73, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x13, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, - 0x2e, 0x70, 0x62, 0x2e, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x52, 0x0b, - 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x73, - 0x68, 0x6f, 0x72, 0x74, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, - 0x73, 0x68, 0x6f, 0x72, 0x74, 0x55, 0x72, 0x6c, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x61, 0x6e, 0x6f, - 0x6e, 0x69, 0x63, 0x61, 0x6c, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x15, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0c, 0x63, 0x61, 0x6e, 0x6f, 0x6e, 0x69, 0x63, 0x61, 0x6c, 0x55, 0x72, 0x6c, 0x12, 0x14, 0x0a, - 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x16, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, - 0x74, 0x6c, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x18, 0x17, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x12, 0x20, 0x0a, 0x0b, 0x64, - 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x18, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, - 0x0a, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x19, 0x20, 0x03, 0x28, - 0x09, 0x52, 0x09, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x54, 0x79, 0x70, 0x65, 0x12, 0x31, 0x0a, 0x0c, - 0x72, 0x65, 0x70, 0x6f, 0x73, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x1a, 0x20, 0x01, + 0x75, 0x65, 0x22, 0x23, 0x0a, 0x0b, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, + 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x21, 0x0a, 0x09, 0x42, 0x6f, 0x6f, 0x6c, 0x56, + 0x61, 0x6c, 0x75, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x23, 0x0a, 0x0b, 0x55, 0x49, + 0x6e, 0x74, 0x33, 0x32, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, + 0x75, 0x0a, 0x0a, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x12, 0x21, 0x0a, + 0x02, 0x6f, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x11, 0x2e, 0x70, 0x62, 0x2e, 0x52, + 0x61, 0x6e, 0x67, 0x65, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x2e, 0x4f, 0x70, 0x52, 0x02, 0x6f, 0x70, + 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x2e, 0x0a, 0x02, 0x4f, 0x70, 0x12, 0x06, 0x0a, 0x02, + 0x45, 0x51, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x4c, 0x54, 0x45, 0x10, 0x01, 0x12, 0x07, 0x0a, + 0x03, 0x47, 0x54, 0x45, 0x10, 0x02, 0x12, 0x06, 0x0a, 0x02, 0x4c, 0x54, 0x10, 0x03, 0x12, 0x06, + 0x0a, 0x02, 0x47, 0x54, 0x10, 0x04, 0x22, 0xe0, 0x11, 0x0a, 0x0d, 0x53, 0x65, 0x61, 0x72, 0x63, + 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x08, 0x63, 0x6c, 0x61, 0x69, + 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x62, 0x2e, + 0x49, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x69, 0x62, 0x6c, 0x65, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x52, + 0x07, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x49, 0x64, 0x12, 0x32, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6e, + 0x6e, 0x65, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, + 0x62, 0x2e, 0x49, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x69, 0x62, 0x6c, 0x65, 0x46, 0x69, 0x65, 0x6c, + 0x64, 0x52, 0x09, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, + 0x74, 0x65, 0x78, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x65, 0x78, 0x74, + 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, + 0x62, 0x79, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x42, + 0x79, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x0d, 0x52, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x69, 0x73, 0x5f, + 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x18, 0x07, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x0d, 0x69, 0x73, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x69, 0x6e, 0x67, + 0x12, 0x31, 0x0a, 0x15, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x74, 0x61, 0x6b, 0x65, 0x5f, 0x6f, 0x76, + 0x65, 0x72, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x12, 0x6c, 0x61, 0x73, 0x74, 0x54, 0x61, 0x6b, 0x65, 0x4f, 0x76, 0x65, 0x72, 0x48, 0x65, 0x69, + 0x67, 0x68, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x5f, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x4e, 0x61, + 0x6d, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, + 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6e, 0x6f, 0x72, + 0x6d, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x2f, 0x0a, 0x0b, 0x74, + 0x78, 0x5f, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x0e, 0x2e, 0x70, 0x62, 0x2e, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x46, 0x69, 0x65, 0x6c, 0x64, + 0x52, 0x0a, 0x74, 0x78, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x26, 0x0a, 0x06, + 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x70, + 0x62, 0x2e, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x52, 0x06, 0x61, 0x6d, + 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, + 0x70, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x70, 0x62, 0x2e, 0x52, 0x61, 0x6e, + 0x67, 0x65, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, + 0x6d, 0x70, 0x12, 0x3d, 0x0a, 0x12, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, + 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, + 0x2e, 0x70, 0x62, 0x2e, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x52, 0x11, + 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, + 0x70, 0x12, 0x26, 0x0a, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x0f, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x0e, 0x2e, 0x70, 0x62, 0x2e, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x46, 0x69, 0x65, 0x6c, + 0x64, 0x52, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x37, 0x0a, 0x0f, 0x63, 0x72, 0x65, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x70, 0x62, 0x2e, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x46, 0x69, 0x65, - 0x6c, 0x64, 0x52, 0x0b, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, - 0x1f, 0x0a, 0x0b, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x1b, - 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x54, 0x79, 0x70, 0x65, - 0x12, 0x1d, 0x0a, 0x0a, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x1c, - 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x54, 0x79, 0x70, 0x65, 0x12, - 0x2d, 0x0a, 0x0a, 0x66, 0x65, 0x65, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x1d, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x70, 0x62, 0x2e, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x46, 0x69, - 0x65, 0x6c, 0x64, 0x52, 0x09, 0x66, 0x65, 0x65, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x21, - 0x0a, 0x0c, 0x66, 0x65, 0x65, 0x5f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x1e, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x66, 0x65, 0x65, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, - 0x79, 0x12, 0x2a, 0x0a, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x1f, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x70, 0x62, 0x2e, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x46, 0x69, - 0x65, 0x6c, 0x64, 0x52, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2a, 0x0a, - 0x11, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x74, 0x65, 0x64, 0x5f, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x5f, - 0x69, 0x64, 0x18, 0x20, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x74, - 0x65, 0x64, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x49, 0x64, 0x12, 0x2f, 0x0a, 0x0b, 0x63, 0x65, 0x6e, - 0x73, 0x6f, 0x72, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x21, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, - 0x2e, 0x70, 0x62, 0x2e, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x52, 0x0a, - 0x63, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x54, 0x79, 0x70, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x63, 0x6c, - 0x61, 0x69, 0x6d, 0x73, 0x5f, 0x69, 0x6e, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x18, - 0x22, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x49, 0x6e, 0x43, - 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x31, 0x0a, 0x0c, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, - 0x6c, 0x5f, 0x6a, 0x6f, 0x69, 0x6e, 0x18, 0x23, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x70, - 0x62, 0x2e, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x52, 0x0b, 0x63, 0x68, - 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x4a, 0x6f, 0x69, 0x6e, 0x12, 0x3b, 0x0a, 0x12, 0x69, 0x73, 0x5f, - 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x18, - 0x24, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x70, 0x62, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, - 0x61, 0x6c, 0x75, 0x65, 0x52, 0x10, 0x69, 0x73, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, - 0x65, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x12, 0x39, 0x0a, 0x10, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, - 0x69, 0x76, 0x65, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x25, 0x20, 0x01, 0x28, 0x0b, + 0x6c, 0x64, 0x52, 0x0e, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x65, 0x69, 0x67, + 0x68, 0x74, 0x12, 0x3b, 0x0a, 0x11, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, + 0x70, 0x62, 0x2e, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x52, 0x10, 0x61, + 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, + 0x3b, 0x0a, 0x11, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x68, 0x65, + 0x69, 0x67, 0x68, 0x74, 0x18, 0x12, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x70, 0x62, 0x2e, + 0x52, 0x61, 0x6e, 0x67, 0x65, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x52, 0x10, 0x65, 0x78, 0x70, 0x69, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x31, 0x0a, 0x0c, + 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x13, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x70, 0x62, 0x2e, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x46, 0x69, 0x65, + 0x6c, 0x64, 0x52, 0x0b, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, + 0x1b, 0x0a, 0x09, 0x73, 0x68, 0x6f, 0x72, 0x74, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x14, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x08, 0x73, 0x68, 0x6f, 0x72, 0x74, 0x55, 0x72, 0x6c, 0x12, 0x23, 0x0a, 0x0d, + 0x63, 0x61, 0x6e, 0x6f, 0x6e, 0x69, 0x63, 0x61, 0x6c, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x15, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x61, 0x6e, 0x6f, 0x6e, 0x69, 0x63, 0x61, 0x6c, 0x55, 0x72, + 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x16, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x75, 0x74, 0x68, 0x6f, + 0x72, 0x18, 0x17, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x12, + 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x18, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, + 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, + 0x19, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x54, 0x79, 0x70, 0x65, + 0x12, 0x31, 0x0a, 0x0c, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x18, 0x1a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x70, 0x62, 0x2e, 0x52, 0x61, 0x6e, 0x67, + 0x65, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x52, 0x0b, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x74, 0x43, 0x6f, + 0x75, 0x6e, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x74, 0x79, + 0x70, 0x65, 0x18, 0x1b, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, + 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x5f, 0x74, 0x79, + 0x70, 0x65, 0x18, 0x1c, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x54, + 0x79, 0x70, 0x65, 0x12, 0x2d, 0x0a, 0x0a, 0x66, 0x65, 0x65, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, + 0x74, 0x18, 0x1d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x70, 0x62, 0x2e, 0x52, 0x61, 0x6e, + 0x67, 0x65, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x52, 0x09, 0x66, 0x65, 0x65, 0x41, 0x6d, 0x6f, 0x75, + 0x6e, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x66, 0x65, 0x65, 0x5f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, + 0x63, 0x79, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x66, 0x65, 0x65, 0x43, 0x75, 0x72, + 0x72, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x2a, 0x0a, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x18, 0x1f, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x70, 0x62, 0x2e, 0x52, 0x61, 0x6e, + 0x67, 0x65, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x52, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x12, 0x2a, 0x0a, 0x11, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x74, 0x65, 0x64, 0x5f, 0x63, 0x6c, + 0x61, 0x69, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x20, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x72, 0x65, + 0x70, 0x6f, 0x73, 0x74, 0x65, 0x64, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x49, 0x64, 0x12, 0x2f, 0x0a, + 0x0b, 0x63, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x21, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x70, 0x62, 0x2e, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x46, 0x69, 0x65, + 0x6c, 0x64, 0x52, 0x0a, 0x63, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x54, 0x79, 0x70, 0x65, 0x12, 0x2a, + 0x0a, 0x11, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x5f, 0x69, 0x6e, 0x5f, 0x63, 0x68, 0x61, 0x6e, + 0x6e, 0x65, 0x6c, 0x18, 0x22, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x63, 0x6c, 0x61, 0x69, 0x6d, + 0x73, 0x49, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x31, 0x0a, 0x0c, 0x63, 0x68, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x6a, 0x6f, 0x69, 0x6e, 0x18, 0x23, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x70, 0x62, 0x2e, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x46, 0x69, 0x65, 0x6c, 0x64, - 0x52, 0x0f, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x41, 0x6d, 0x6f, 0x75, 0x6e, - 0x74, 0x12, 0x35, 0x0a, 0x0e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x61, 0x6d, 0x6f, - 0x75, 0x6e, 0x74, 0x18, 0x26, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x70, 0x62, 0x2e, 0x52, - 0x61, 0x6e, 0x67, 0x65, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x52, 0x0d, 0x73, 0x75, 0x70, 0x70, 0x6f, - 0x72, 0x74, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x35, 0x0a, 0x0e, 0x74, 0x72, 0x65, 0x6e, - 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x18, 0x27, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x0e, 0x2e, 0x70, 0x62, 0x2e, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x46, 0x69, 0x65, 0x6c, 0x64, - 0x52, 0x0d, 0x74, 0x72, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x12, - 0x13, 0x0a, 0x05, 0x74, 0x78, 0x5f, 0x69, 0x64, 0x18, 0x2b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, - 0x74, 0x78, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x07, 0x74, 0x78, 0x5f, 0x6e, 0x6f, 0x75, 0x74, 0x18, - 0x2c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x70, 0x62, 0x2e, 0x55, 0x49, 0x6e, 0x74, 0x33, - 0x32, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x06, 0x74, 0x78, 0x4e, 0x6f, 0x75, 0x74, 0x12, 0x1c, - 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x2d, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x29, 0x0a, 0x10, - 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, - 0x18, 0x2e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, - 0x65, 0x44, 0x69, 0x67, 0x65, 0x73, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x70, 0x75, 0x62, 0x6c, 0x69, - 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x2f, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0e, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x42, 0x79, 0x74, 0x65, - 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x5f, - 0x69, 0x64, 0x18, 0x30, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, - 0x4b, 0x65, 0x79, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x6e, 0x79, 0x5f, 0x74, 0x61, 0x67, - 0x73, 0x18, 0x31, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x61, 0x6e, 0x79, 0x54, 0x61, 0x67, 0x73, - 0x12, 0x19, 0x0a, 0x08, 0x61, 0x6c, 0x6c, 0x5f, 0x74, 0x61, 0x67, 0x73, 0x18, 0x32, 0x20, 0x03, - 0x28, 0x09, 0x52, 0x07, 0x61, 0x6c, 0x6c, 0x54, 0x61, 0x67, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x6e, - 0x6f, 0x74, 0x5f, 0x74, 0x61, 0x67, 0x73, 0x18, 0x33, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x6e, - 0x6f, 0x74, 0x54, 0x61, 0x67, 0x73, 0x12, 0x32, 0x0a, 0x15, 0x68, 0x61, 0x73, 0x5f, 0x63, 0x68, - 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, - 0x34, 0x20, 0x01, 0x28, 0x08, 0x52, 0x13, 0x68, 0x61, 0x73, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, - 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x2c, 0x0a, 0x0a, 0x68, 0x61, - 0x73, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x35, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, - 0x2e, 0x70, 0x62, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x09, 0x68, - 0x61, 0x73, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x37, 0x0a, 0x18, 0x6c, 0x69, 0x6d, 0x69, - 0x74, 0x5f, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x63, 0x68, 0x61, - 0x6e, 0x6e, 0x65, 0x6c, 0x18, 0x36, 0x20, 0x01, 0x28, 0x05, 0x52, 0x15, 0x6c, 0x69, 0x6d, 0x69, - 0x74, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x50, 0x65, 0x72, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, - 0x6c, 0x12, 0x23, 0x0a, 0x0d, 0x61, 0x6e, 0x79, 0x5f, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, - 0x65, 0x73, 0x18, 0x37, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x61, 0x6e, 0x79, 0x4c, 0x61, 0x6e, - 0x67, 0x75, 0x61, 0x67, 0x65, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x61, 0x6c, 0x6c, 0x5f, 0x6c, 0x61, - 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x73, 0x18, 0x38, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x61, - 0x6c, 0x6c, 0x4c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x73, 0x12, 0x2b, 0x0a, 0x11, 0x72, - 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x5f, 0x64, 0x75, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, - 0x18, 0x39, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x44, 0x75, - 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x6f, 0x5f, 0x74, - 0x6f, 0x74, 0x61, 0x6c, 0x73, 0x18, 0x3a, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x6e, 0x6f, 0x54, - 0x6f, 0x74, 0x61, 0x6c, 0x73, 0x32, 0xf1, 0x01, 0x0a, 0x03, 0x48, 0x75, 0x62, 0x12, 0x2a, 0x0a, - 0x06, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x12, 0x11, 0x2e, 0x70, 0x62, 0x2e, 0x53, 0x65, 0x61, - 0x72, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0b, 0x2e, 0x70, 0x62, 0x2e, - 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x22, 0x00, 0x12, 0x2b, 0x0a, 0x04, 0x50, 0x69, 0x6e, - 0x67, 0x12, 0x10, 0x2e, 0x70, 0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x4d, 0x65, 0x73, 0x73, + 0x52, 0x0b, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x4a, 0x6f, 0x69, 0x6e, 0x12, 0x3b, 0x0a, + 0x12, 0x69, 0x73, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x76, 0x61, + 0x6c, 0x69, 0x64, 0x18, 0x24, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x70, 0x62, 0x2e, 0x42, + 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x10, 0x69, 0x73, 0x53, 0x69, 0x67, 0x6e, + 0x61, 0x74, 0x75, 0x72, 0x65, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x12, 0x39, 0x0a, 0x10, 0x65, 0x66, + 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x25, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x70, 0x62, 0x2e, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x46, + 0x69, 0x65, 0x6c, 0x64, 0x52, 0x0f, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x41, + 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x35, 0x0a, 0x0e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, + 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x26, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, + 0x70, 0x62, 0x2e, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x52, 0x0d, 0x73, + 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x35, 0x0a, 0x0e, + 0x74, 0x72, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x18, 0x27, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x70, 0x62, 0x2e, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x46, + 0x69, 0x65, 0x6c, 0x64, 0x52, 0x0d, 0x74, 0x72, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x63, + 0x6f, 0x72, 0x65, 0x12, 0x13, 0x0a, 0x05, 0x74, 0x78, 0x5f, 0x69, 0x64, 0x18, 0x2b, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x74, 0x78, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x07, 0x74, 0x78, 0x5f, 0x6e, + 0x6f, 0x75, 0x74, 0x18, 0x2c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x70, 0x62, 0x2e, 0x55, + 0x49, 0x6e, 0x74, 0x33, 0x32, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x06, 0x74, 0x78, 0x4e, 0x6f, + 0x75, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, + 0x2d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, + 0x12, 0x29, 0x0a, 0x10, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x64, 0x69, + 0x67, 0x65, 0x73, 0x74, 0x18, 0x2e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x69, 0x67, 0x6e, + 0x61, 0x74, 0x75, 0x72, 0x65, 0x44, 0x69, 0x67, 0x65, 0x73, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x70, + 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, + 0x2f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, + 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, + 0x6b, 0x65, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x30, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x75, + 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x6e, 0x79, + 0x5f, 0x74, 0x61, 0x67, 0x73, 0x18, 0x31, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x61, 0x6e, 0x79, + 0x54, 0x61, 0x67, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x6c, 0x6c, 0x5f, 0x74, 0x61, 0x67, 0x73, + 0x18, 0x32, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x61, 0x6c, 0x6c, 0x54, 0x61, 0x67, 0x73, 0x12, + 0x19, 0x0a, 0x08, 0x6e, 0x6f, 0x74, 0x5f, 0x74, 0x61, 0x67, 0x73, 0x18, 0x33, 0x20, 0x03, 0x28, + 0x09, 0x52, 0x07, 0x6e, 0x6f, 0x74, 0x54, 0x61, 0x67, 0x73, 0x12, 0x32, 0x0a, 0x15, 0x68, 0x61, + 0x73, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, + 0x75, 0x72, 0x65, 0x18, 0x34, 0x20, 0x01, 0x28, 0x08, 0x52, 0x13, 0x68, 0x61, 0x73, 0x43, 0x68, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x2c, + 0x0a, 0x0a, 0x68, 0x61, 0x73, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x35, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x70, 0x62, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, + 0x65, 0x52, 0x09, 0x68, 0x61, 0x73, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x37, 0x0a, 0x18, + 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x5f, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x5f, 0x70, 0x65, 0x72, + 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x18, 0x36, 0x20, 0x01, 0x28, 0x05, 0x52, 0x15, + 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x50, 0x65, 0x72, 0x43, 0x68, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x23, 0x0a, 0x0d, 0x61, 0x6e, 0x79, 0x5f, 0x6c, 0x61, 0x6e, + 0x67, 0x75, 0x61, 0x67, 0x65, 0x73, 0x18, 0x37, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x61, 0x6e, + 0x79, 0x4c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x61, 0x6c, + 0x6c, 0x5f, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x73, 0x18, 0x38, 0x20, 0x03, 0x28, + 0x09, 0x52, 0x0c, 0x61, 0x6c, 0x6c, 0x4c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x73, 0x12, + 0x2b, 0x0a, 0x11, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x5f, 0x64, 0x75, 0x70, 0x6c, 0x69, 0x63, + 0x61, 0x74, 0x65, 0x73, 0x18, 0x39, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x72, 0x65, 0x6d, 0x6f, + 0x76, 0x65, 0x44, 0x75, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x12, 0x1b, 0x0a, 0x09, + 0x6e, 0x6f, 0x5f, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x73, 0x18, 0x3a, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x08, 0x6e, 0x6f, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x73, 0x32, 0xcc, 0x03, 0x0a, 0x03, 0x48, 0x75, + 0x62, 0x12, 0x2a, 0x0a, 0x06, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x12, 0x11, 0x2e, 0x70, 0x62, + 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0b, + 0x2e, 0x70, 0x62, 0x2e, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x22, 0x00, 0x12, 0x2b, 0x0a, + 0x04, 0x50, 0x69, 0x6e, 0x67, 0x12, 0x10, 0x2e, 0x70, 0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, + 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x0f, 0x2e, 0x70, 0x62, 0x2e, 0x53, 0x74, 0x72, + 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x00, 0x12, 0x2d, 0x0a, 0x05, 0x48, 0x65, + 0x6c, 0x6c, 0x6f, 0x12, 0x10, 0x2e, 0x70, 0x62, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x4d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x10, 0x2e, 0x70, 0x62, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, + 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x12, 0x2f, 0x0a, 0x07, 0x41, 0x64, 0x64, + 0x50, 0x65, 0x65, 0x72, 0x12, 0x11, 0x2e, 0x70, 0x62, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, + 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x0f, 0x2e, 0x70, 0x62, 0x2e, 0x53, 0x74, 0x72, + 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x00, 0x12, 0x42, 0x0a, 0x16, 0x50, 0x65, + 0x65, 0x72, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x53, 0x74, 0x72, 0x65, 0x61, + 0x6d, 0x69, 0x6e, 0x67, 0x12, 0x11, 0x2e, 0x70, 0x62, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, + 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x11, 0x2e, 0x70, 0x62, 0x2e, 0x53, 0x65, 0x72, + 0x76, 0x65, 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12, 0x35, + 0x0a, 0x0d, 0x50, 0x65, 0x65, 0x72, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x12, + 0x11, 0x2e, 0x70, 0x62, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x1a, 0x0f, 0x2e, 0x70, 0x62, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, + 0x6c, 0x75, 0x65, 0x22, 0x00, 0x12, 0x2e, 0x0a, 0x07, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, + 0x12, 0x10, 0x2e, 0x70, 0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x1a, 0x0f, 0x2e, 0x70, 0x62, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, + 0x6c, 0x75, 0x65, 0x22, 0x00, 0x12, 0x2f, 0x0a, 0x08, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, + 0x73, 0x12, 0x10, 0x2e, 0x70, 0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x0f, 0x2e, 0x70, 0x62, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, - 0x61, 0x6c, 0x75, 0x65, 0x22, 0x00, 0x12, 0x2e, 0x0a, 0x07, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, - 0x6e, 0x12, 0x10, 0x2e, 0x70, 0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x4d, 0x65, 0x73, 0x73, - 0x61, 0x67, 0x65, 0x1a, 0x0f, 0x2e, 0x70, 0x62, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, - 0x61, 0x6c, 0x75, 0x65, 0x22, 0x00, 0x12, 0x2f, 0x0a, 0x08, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, - 0x65, 0x73, 0x12, 0x10, 0x2e, 0x70, 0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x4d, 0x65, 0x73, - 0x73, 0x61, 0x67, 0x65, 0x1a, 0x0f, 0x2e, 0x70, 0x62, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, - 0x56, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x00, 0x12, 0x30, 0x0a, 0x09, 0x42, 0x72, 0x6f, 0x61, 0x64, - 0x63, 0x61, 0x73, 0x74, 0x12, 0x10, 0x2e, 0x70, 0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x4d, - 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x0f, 0x2e, 0x70, 0x62, 0x2e, 0x55, 0x49, 0x6e, 0x74, - 0x33, 0x32, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x00, 0x42, 0x26, 0x5a, 0x24, 0x67, 0x69, 0x74, - 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x62, 0x72, 0x79, 0x69, 0x6f, 0x2f, 0x68, - 0x75, 0x62, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x67, 0x6f, 0x2f, 0x70, - 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x61, 0x6c, 0x75, 0x65, 0x22, 0x00, 0x12, 0x30, 0x0a, 0x09, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63, + 0x61, 0x73, 0x74, 0x12, 0x10, 0x2e, 0x70, 0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x4d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x0f, 0x2e, 0x70, 0x62, 0x2e, 0x55, 0x49, 0x6e, 0x74, 0x33, + 0x32, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x00, 0x42, 0x26, 0x5a, 0x24, 0x67, 0x69, 0x74, 0x68, + 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x62, 0x72, 0x79, 0x69, 0x6f, 0x2f, 0x68, 0x75, + 0x62, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x67, 0x6f, 0x2f, 0x70, 0x62, + 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1043,57 +1185,68 @@ func file_hub_proto_rawDescGZIP() []byte { } var file_hub_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_hub_proto_msgTypes = make([]protoimpl.MessageInfo, 7) +var file_hub_proto_msgTypes = make([]protoimpl.MessageInfo, 9) var file_hub_proto_goTypes = []interface{}{ (RangeField_Op)(0), // 0: pb.RangeField.Op (*EmptyMessage)(nil), // 1: pb.EmptyMessage - (*InvertibleField)(nil), // 2: pb.InvertibleField - (*StringValue)(nil), // 3: pb.StringValue - (*BoolValue)(nil), // 4: pb.BoolValue - (*UInt32Value)(nil), // 5: pb.UInt32Value - (*RangeField)(nil), // 6: pb.RangeField - (*SearchRequest)(nil), // 7: pb.SearchRequest - (*Outputs)(nil), // 8: pb.Outputs + (*ServerMessage)(nil), // 2: pb.ServerMessage + (*HelloMessage)(nil), // 3: pb.HelloMessage + (*InvertibleField)(nil), // 4: pb.InvertibleField + (*StringValue)(nil), // 5: pb.StringValue + (*BoolValue)(nil), // 6: pb.BoolValue + (*UInt32Value)(nil), // 7: pb.UInt32Value + (*RangeField)(nil), // 8: pb.RangeField + (*SearchRequest)(nil), // 9: pb.SearchRequest + (*Outputs)(nil), // 10: pb.Outputs } var file_hub_proto_depIdxs = []int32{ - 0, // 0: pb.RangeField.op:type_name -> pb.RangeField.Op - 2, // 1: pb.SearchRequest.claim_id:type_name -> pb.InvertibleField - 2, // 2: pb.SearchRequest.channel_id:type_name -> pb.InvertibleField - 6, // 3: pb.SearchRequest.tx_position:type_name -> pb.RangeField - 6, // 4: pb.SearchRequest.amount:type_name -> pb.RangeField - 6, // 5: pb.SearchRequest.timestamp:type_name -> pb.RangeField - 6, // 6: pb.SearchRequest.creation_timestamp:type_name -> pb.RangeField - 6, // 7: pb.SearchRequest.height:type_name -> pb.RangeField - 6, // 8: pb.SearchRequest.creation_height:type_name -> pb.RangeField - 6, // 9: pb.SearchRequest.activation_height:type_name -> pb.RangeField - 6, // 10: pb.SearchRequest.expiration_height:type_name -> pb.RangeField - 6, // 11: pb.SearchRequest.release_time:type_name -> pb.RangeField - 6, // 12: pb.SearchRequest.repost_count:type_name -> pb.RangeField - 6, // 13: pb.SearchRequest.fee_amount:type_name -> pb.RangeField - 6, // 14: pb.SearchRequest.duration:type_name -> pb.RangeField - 6, // 15: pb.SearchRequest.censor_type:type_name -> pb.RangeField - 6, // 16: pb.SearchRequest.channel_join:type_name -> pb.RangeField - 4, // 17: pb.SearchRequest.is_signature_valid:type_name -> pb.BoolValue - 6, // 18: pb.SearchRequest.effective_amount:type_name -> pb.RangeField - 6, // 19: pb.SearchRequest.support_amount:type_name -> pb.RangeField - 6, // 20: pb.SearchRequest.trending_score:type_name -> pb.RangeField - 5, // 21: pb.SearchRequest.tx_nout:type_name -> pb.UInt32Value - 4, // 22: pb.SearchRequest.has_source:type_name -> pb.BoolValue - 7, // 23: pb.Hub.Search:input_type -> pb.SearchRequest - 1, // 24: pb.Hub.Ping:input_type -> pb.EmptyMessage - 1, // 25: pb.Hub.Version:input_type -> pb.EmptyMessage - 1, // 26: pb.Hub.Features:input_type -> pb.EmptyMessage - 1, // 27: pb.Hub.Broadcast:input_type -> pb.EmptyMessage - 8, // 28: pb.Hub.Search:output_type -> pb.Outputs - 3, // 29: pb.Hub.Ping:output_type -> pb.StringValue - 3, // 30: pb.Hub.Version:output_type -> pb.StringValue - 3, // 31: pb.Hub.Features:output_type -> pb.StringValue - 5, // 32: pb.Hub.Broadcast:output_type -> pb.UInt32Value - 28, // [28:33] is the sub-list for method output_type - 23, // [23:28] is the sub-list for method input_type - 23, // [23:23] is the sub-list for extension type_name - 23, // [23:23] is the sub-list for extension extendee - 0, // [0:23] is the sub-list for field type_name + 2, // 0: pb.HelloMessage.servers:type_name -> pb.ServerMessage + 0, // 1: pb.RangeField.op:type_name -> pb.RangeField.Op + 4, // 2: pb.SearchRequest.claim_id:type_name -> pb.InvertibleField + 4, // 3: pb.SearchRequest.channel_id:type_name -> pb.InvertibleField + 8, // 4: pb.SearchRequest.tx_position:type_name -> pb.RangeField + 8, // 5: pb.SearchRequest.amount:type_name -> pb.RangeField + 8, // 6: pb.SearchRequest.timestamp:type_name -> pb.RangeField + 8, // 7: pb.SearchRequest.creation_timestamp:type_name -> pb.RangeField + 8, // 8: pb.SearchRequest.height:type_name -> pb.RangeField + 8, // 9: pb.SearchRequest.creation_height:type_name -> pb.RangeField + 8, // 10: pb.SearchRequest.activation_height:type_name -> pb.RangeField + 8, // 11: pb.SearchRequest.expiration_height:type_name -> pb.RangeField + 8, // 12: pb.SearchRequest.release_time:type_name -> pb.RangeField + 8, // 13: pb.SearchRequest.repost_count:type_name -> pb.RangeField + 8, // 14: pb.SearchRequest.fee_amount:type_name -> pb.RangeField + 8, // 15: pb.SearchRequest.duration:type_name -> pb.RangeField + 8, // 16: pb.SearchRequest.censor_type:type_name -> pb.RangeField + 8, // 17: pb.SearchRequest.channel_join:type_name -> pb.RangeField + 6, // 18: pb.SearchRequest.is_signature_valid:type_name -> pb.BoolValue + 8, // 19: pb.SearchRequest.effective_amount:type_name -> pb.RangeField + 8, // 20: pb.SearchRequest.support_amount:type_name -> pb.RangeField + 8, // 21: pb.SearchRequest.trending_score:type_name -> pb.RangeField + 7, // 22: pb.SearchRequest.tx_nout:type_name -> pb.UInt32Value + 6, // 23: pb.SearchRequest.has_source:type_name -> pb.BoolValue + 9, // 24: pb.Hub.Search:input_type -> pb.SearchRequest + 1, // 25: pb.Hub.Ping:input_type -> pb.EmptyMessage + 3, // 26: pb.Hub.Hello:input_type -> pb.HelloMessage + 2, // 27: pb.Hub.AddPeer:input_type -> pb.ServerMessage + 2, // 28: pb.Hub.PeerSubscribeStreaming:input_type -> pb.ServerMessage + 2, // 29: pb.Hub.PeerSubscribe:input_type -> pb.ServerMessage + 1, // 30: pb.Hub.Version:input_type -> pb.EmptyMessage + 1, // 31: pb.Hub.Features:input_type -> pb.EmptyMessage + 1, // 32: pb.Hub.Broadcast:input_type -> pb.EmptyMessage + 10, // 33: pb.Hub.Search:output_type -> pb.Outputs + 5, // 34: pb.Hub.Ping:output_type -> pb.StringValue + 3, // 35: pb.Hub.Hello:output_type -> pb.HelloMessage + 5, // 36: pb.Hub.AddPeer:output_type -> pb.StringValue + 2, // 37: pb.Hub.PeerSubscribeStreaming:output_type -> pb.ServerMessage + 5, // 38: pb.Hub.PeerSubscribe:output_type -> pb.StringValue + 5, // 39: pb.Hub.Version:output_type -> pb.StringValue + 5, // 40: pb.Hub.Features:output_type -> pb.StringValue + 7, // 41: pb.Hub.Broadcast:output_type -> pb.UInt32Value + 33, // [33:42] is the sub-list for method output_type + 24, // [24:33] is the sub-list for method input_type + 24, // [24:24] is the sub-list for extension type_name + 24, // [24:24] is the sub-list for extension extendee + 0, // [0:24] is the sub-list for field type_name } func init() { file_hub_proto_init() } @@ -1116,7 +1269,7 @@ func file_hub_proto_init() { } } file_hub_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*InvertibleField); i { + switch v := v.(*ServerMessage); i { case 0: return &v.state case 1: @@ -1128,7 +1281,7 @@ func file_hub_proto_init() { } } file_hub_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*StringValue); i { + switch v := v.(*HelloMessage); i { case 0: return &v.state case 1: @@ -1140,7 +1293,7 @@ func file_hub_proto_init() { } } file_hub_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*BoolValue); i { + switch v := v.(*InvertibleField); i { case 0: return &v.state case 1: @@ -1152,7 +1305,7 @@ func file_hub_proto_init() { } } file_hub_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*UInt32Value); i { + switch v := v.(*StringValue); i { case 0: return &v.state case 1: @@ -1164,7 +1317,7 @@ func file_hub_proto_init() { } } file_hub_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*RangeField); i { + switch v := v.(*BoolValue); i { case 0: return &v.state case 1: @@ -1176,6 +1329,30 @@ func file_hub_proto_init() { } } file_hub_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UInt32Value); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_hub_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RangeField); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_hub_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*SearchRequest); i { case 0: return &v.state @@ -1194,7 +1371,7 @@ func file_hub_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_hub_proto_rawDesc, NumEnums: 1, - NumMessages: 7, + NumMessages: 9, NumExtensions: 0, NumServices: 1, }, diff --git a/protobuf/go/hub_grpc.pb.go b/protobuf/go/hub_grpc.pb.go index e4325da..c338102 100644 --- a/protobuf/go/hub_grpc.pb.go +++ b/protobuf/go/hub_grpc.pb.go @@ -20,6 +20,10 @@ const _ = grpc.SupportPackageIsVersion7 type HubClient interface { Search(ctx context.Context, in *SearchRequest, opts ...grpc.CallOption) (*Outputs, error) Ping(ctx context.Context, in *EmptyMessage, opts ...grpc.CallOption) (*StringValue, error) + Hello(ctx context.Context, in *HelloMessage, opts ...grpc.CallOption) (*HelloMessage, error) + AddPeer(ctx context.Context, in *ServerMessage, opts ...grpc.CallOption) (*StringValue, error) + PeerSubscribeStreaming(ctx context.Context, in *ServerMessage, opts ...grpc.CallOption) (Hub_PeerSubscribeStreamingClient, error) + PeerSubscribe(ctx context.Context, in *ServerMessage, opts ...grpc.CallOption) (*StringValue, error) Version(ctx context.Context, in *EmptyMessage, opts ...grpc.CallOption) (*StringValue, error) Features(ctx context.Context, in *EmptyMessage, opts ...grpc.CallOption) (*StringValue, error) Broadcast(ctx context.Context, in *EmptyMessage, opts ...grpc.CallOption) (*UInt32Value, error) @@ -51,6 +55,65 @@ func (c *hubClient) Ping(ctx context.Context, in *EmptyMessage, opts ...grpc.Cal return out, nil } +func (c *hubClient) Hello(ctx context.Context, in *HelloMessage, opts ...grpc.CallOption) (*HelloMessage, error) { + out := new(HelloMessage) + err := c.cc.Invoke(ctx, "/pb.Hub/Hello", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *hubClient) AddPeer(ctx context.Context, in *ServerMessage, opts ...grpc.CallOption) (*StringValue, error) { + out := new(StringValue) + err := c.cc.Invoke(ctx, "/pb.Hub/AddPeer", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *hubClient) PeerSubscribeStreaming(ctx context.Context, in *ServerMessage, opts ...grpc.CallOption) (Hub_PeerSubscribeStreamingClient, error) { + stream, err := c.cc.NewStream(ctx, &Hub_ServiceDesc.Streams[0], "/pb.Hub/PeerSubscribeStreaming", opts...) + if err != nil { + return nil, err + } + x := &hubPeerSubscribeStreamingClient{stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +type Hub_PeerSubscribeStreamingClient interface { + Recv() (*ServerMessage, error) + grpc.ClientStream +} + +type hubPeerSubscribeStreamingClient struct { + grpc.ClientStream +} + +func (x *hubPeerSubscribeStreamingClient) Recv() (*ServerMessage, error) { + m := new(ServerMessage) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func (c *hubClient) PeerSubscribe(ctx context.Context, in *ServerMessage, opts ...grpc.CallOption) (*StringValue, error) { + out := new(StringValue) + err := c.cc.Invoke(ctx, "/pb.Hub/PeerSubscribe", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *hubClient) Version(ctx context.Context, in *EmptyMessage, opts ...grpc.CallOption) (*StringValue, error) { out := new(StringValue) err := c.cc.Invoke(ctx, "/pb.Hub/Version", in, out, opts...) @@ -84,6 +147,10 @@ func (c *hubClient) Broadcast(ctx context.Context, in *EmptyMessage, opts ...grp type HubServer interface { Search(context.Context, *SearchRequest) (*Outputs, error) Ping(context.Context, *EmptyMessage) (*StringValue, error) + Hello(context.Context, *HelloMessage) (*HelloMessage, error) + AddPeer(context.Context, *ServerMessage) (*StringValue, error) + PeerSubscribeStreaming(*ServerMessage, Hub_PeerSubscribeStreamingServer) error + PeerSubscribe(context.Context, *ServerMessage) (*StringValue, error) Version(context.Context, *EmptyMessage) (*StringValue, error) Features(context.Context, *EmptyMessage) (*StringValue, error) Broadcast(context.Context, *EmptyMessage) (*UInt32Value, error) @@ -100,6 +167,18 @@ func (UnimplementedHubServer) Search(context.Context, *SearchRequest) (*Outputs, func (UnimplementedHubServer) Ping(context.Context, *EmptyMessage) (*StringValue, error) { return nil, status.Errorf(codes.Unimplemented, "method Ping not implemented") } +func (UnimplementedHubServer) Hello(context.Context, *HelloMessage) (*HelloMessage, error) { + return nil, status.Errorf(codes.Unimplemented, "method Hello not implemented") +} +func (UnimplementedHubServer) AddPeer(context.Context, *ServerMessage) (*StringValue, error) { + return nil, status.Errorf(codes.Unimplemented, "method AddPeer not implemented") +} +func (UnimplementedHubServer) PeerSubscribeStreaming(*ServerMessage, Hub_PeerSubscribeStreamingServer) error { + return status.Errorf(codes.Unimplemented, "method PeerSubscribeStreaming not implemented") +} +func (UnimplementedHubServer) PeerSubscribe(context.Context, *ServerMessage) (*StringValue, error) { + return nil, status.Errorf(codes.Unimplemented, "method PeerSubscribe not implemented") +} func (UnimplementedHubServer) Version(context.Context, *EmptyMessage) (*StringValue, error) { return nil, status.Errorf(codes.Unimplemented, "method Version not implemented") } @@ -158,6 +237,81 @@ func _Hub_Ping_Handler(srv interface{}, ctx context.Context, dec func(interface{ return interceptor(ctx, in, info, handler) } +func _Hub_Hello_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(HelloMessage) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(HubServer).Hello(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/pb.Hub/Hello", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HubServer).Hello(ctx, req.(*HelloMessage)) + } + return interceptor(ctx, in, info, handler) +} + +func _Hub_AddPeer_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ServerMessage) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(HubServer).AddPeer(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/pb.Hub/AddPeer", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HubServer).AddPeer(ctx, req.(*ServerMessage)) + } + return interceptor(ctx, in, info, handler) +} + +func _Hub_PeerSubscribeStreaming_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(ServerMessage) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(HubServer).PeerSubscribeStreaming(m, &hubPeerSubscribeStreamingServer{stream}) +} + +type Hub_PeerSubscribeStreamingServer interface { + Send(*ServerMessage) error + grpc.ServerStream +} + +type hubPeerSubscribeStreamingServer struct { + grpc.ServerStream +} + +func (x *hubPeerSubscribeStreamingServer) Send(m *ServerMessage) error { + return x.ServerStream.SendMsg(m) +} + +func _Hub_PeerSubscribe_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ServerMessage) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(HubServer).PeerSubscribe(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/pb.Hub/PeerSubscribe", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HubServer).PeerSubscribe(ctx, req.(*ServerMessage)) + } + return interceptor(ctx, in, info, handler) +} + func _Hub_Version_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(EmptyMessage) if err := dec(in); err != nil { @@ -227,6 +381,18 @@ var Hub_ServiceDesc = grpc.ServiceDesc{ MethodName: "Ping", Handler: _Hub_Ping_Handler, }, + { + MethodName: "Hello", + Handler: _Hub_Hello_Handler, + }, + { + MethodName: "AddPeer", + Handler: _Hub_AddPeer_Handler, + }, + { + MethodName: "PeerSubscribe", + Handler: _Hub_PeerSubscribe_Handler, + }, { MethodName: "Version", Handler: _Hub_Version_Handler, @@ -240,6 +406,12 @@ var Hub_ServiceDesc = grpc.ServiceDesc{ Handler: _Hub_Broadcast_Handler, }, }, - Streams: []grpc.StreamDesc{}, + Streams: []grpc.StreamDesc{ + { + StreamName: "PeerSubscribeStreaming", + Handler: _Hub_PeerSubscribeStreaming_Handler, + ServerStreams: true, + }, + }, Metadata: "hub.proto", } diff --git a/scripts/version.py b/scripts/version.py index faceda0..abe60fc 100644 --- a/scripts/version.py +++ b/scripts/version.py @@ -24,7 +24,11 @@ AREA_RENAME = { def build_upload_binary(release: github3.repos.release.Release) -> None: # os.chdir(absolute_path) - os.system("go build .") + # os.system("go build .") + cmd = f'CGO_ENABLED=0 go build -v -ldflags "-X github.com/lbryio/hub/meta.Version={release.name}"' + print(cmd) + # os.system("go build .") + os.system(cmd) with open("./hub", "rb") as f: release.upload_asset("binary", "hub", f) @@ -225,9 +229,11 @@ def release(args): print(body.getvalue()) if unlabeled: - print('The following PRs were skipped and not included in changelog:') - for skipped in unlabeled: - print(skipped) + w('') + print('The following PRs were unlabeled and *will* be included in changelog:') + for notskipped in unlabeled: + print(notskipped) + w(notskipped) if fixups: print('The following PRs were marked as fixups and not included in changelog:') diff --git a/server/args.go b/server/args.go new file mode 100644 index 0000000..12cdea4 --- /dev/null +++ b/server/args.go @@ -0,0 +1,200 @@ +package server + +import ( + "log" + "os" + "strings" + + "github.com/akamensky/argparse" + pb "github.com/lbryio/hub/protobuf/go" +) + +const ( + ServeCmd = iota + SearchCmd = iota +) + +// Args struct contains the arguments to the hub server. +type Args struct { + CmdType int + Host string + Port string + UDPPort string + EsHost string + EsPort string + DisableEs bool + PrometheusPort string + EsIndex string + Debug bool + RefreshDelta int + CacheTTL int + PeerFile string + Country string + StartPeerAdder bool + StartPrometheus bool + StartUDP bool + WritePeers bool +} + +const ( + DefaultHost = "0.0.0.0" + DefaultPort = "50051" + DefaultUdpPort = "41119" + DefaultEsHost = "http://localhost" + DefaultEsIndex = "claims" + DefaultEsPort = "9200" + DefaultPrometheusPort = "2112" + DefaultRefreshDelta = 5 + DefaultCacheTTL = 5 + DefaultPeerFile = "peers.txt" + DefaultCountry = "US" + DefaultStartPeerAdder = true + DefaultStartPrometheus = true + DefaultStartUDP = true + DefaultWritePeers = true +) + +// GetEnvironment takes the environment variables as an array of strings +// and a getkeyval function to turn it into a map. +func GetEnvironment(data []string, getkeyval func(item string) (key, val string)) map[string]string { + items := make(map[string]string) + for _, item := range data { + key, val := getkeyval(item) + items[key] = val + } + return items +} + +// GetEnvironmentStandard gets the environment variables as a map. +func GetEnvironmentStandard() map[string]string { + return GetEnvironment(os.Environ(), func(item string) (key, val string) { + splits := strings.Split(item, "=") + key = splits[0] + val = splits[1] + return + }) +} + +// ParseArgs parses the command line arguments when started the hub server. +func ParseArgs(searchRequest *pb.SearchRequest) *Args { + + environment := GetEnvironmentStandard() + parser := argparse.NewParser("hub", "hub server and client") + + serveCmd := parser.NewCommand("serve", "start the hub server") + searchCmd := parser.NewCommand("search", "claim search") + debug := parser.Flag("", "debug", &argparse.Options{Required: false, Help: "enable debug logging", Default: false}) + disableEs := parser.Flag("", "disable-es", &argparse.Options{Required: false, Help: "Disable elastic search, for running/testing independently", Default: false}) + + host := parser.String("", "rpchost", &argparse.Options{Required: false, Help: "RPC host", Default: DefaultHost}) + port := parser.String("", "rpcport", &argparse.Options{Required: false, Help: "RPC port", Default: DefaultPort}) + esHost := parser.String("", "eshost", &argparse.Options{Required: false, Help: "elasticsearch host", Default: DefaultEsHost}) + esPort := parser.String("", "esport", &argparse.Options{Required: false, Help: "elasticsearch port", Default: DefaultEsPort}) + udpPort := parser.String("", "uspport", &argparse.Options{Required: false, Help: "udp ping port", Default: DefaultUdpPort}) + prometheusPort := parser.String("", "prometheus-port", &argparse.Options{Required: false, Help: "prometheus port", Default: DefaultPrometheusPort}) + esIndex := parser.String("", "esindex", &argparse.Options{Required: false, Help: "elasticsearch index name", Default: DefaultEsIndex}) + refreshDelta := parser.Int("", "refresh-delta", &argparse.Options{Required: false, Help: "elasticsearch index refresh delta in seconds", Default: DefaultRefreshDelta}) + cacheTTL := parser.Int("", "cachettl", &argparse.Options{Required: false, Help: "Cache TTL in minutes", Default: DefaultCacheTTL}) + peerFile := parser.String("", "peerfile", &argparse.Options{Required: false, Help: "Initial peer file for federation", Default: DefaultPeerFile}) + country := parser.String("", "country", &argparse.Options{Required: false, Help: "Country this node is running in. Default US.", Default: DefaultCountry}) + startPeerAdder := parser.Flag("", "start-peer-adder", &argparse.Options{Required: false, Help: "Start peer adder service", Default: DefaultStartPeerAdder}) + startPrometheus := parser.Flag("", "start-prometheus", &argparse.Options{Required: false, Help: "Start prometheus server", Default: DefaultStartPrometheus}) + startUdp := parser.Flag("", "start-udp", &argparse.Options{Required: false, Help: "Start UDP ping server", Default: DefaultStartUDP}) + writePeers := parser.Flag("", "write-peers", &argparse.Options{Required: false, Help: "Write peer to disk as we learn about them", Default: DefaultWritePeers}) + + text := parser.String("", "text", &argparse.Options{Required: false, Help: "text query"}) + name := parser.String("", "name", &argparse.Options{Required: false, Help: "name"}) + claimType := parser.String("", "claim_type", &argparse.Options{Required: false, Help: "claim_type"}) + id := parser.String("", "id", &argparse.Options{Required: false, Help: "id"}) + author := parser.String("", "author", &argparse.Options{Required: false, Help: "author"}) + title := parser.String("", "title", &argparse.Options{Required: false, Help: "title"}) + description := parser.String("", "description", &argparse.Options{Required: false, Help: "description"}) + channelId := parser.String("", "channel_id", &argparse.Options{Required: false, Help: "channel id"}) + channelIds := parser.StringList("", "channel_ids", &argparse.Options{Required: false, Help: "channel ids"}) + + // Now parse the arguments + err := parser.Parse(os.Args) + if err != nil { + log.Fatalln(parser.Usage(err)) + } + + args := &Args{ + CmdType: SearchCmd, + Host: *host, + Port: *port, + EsHost: *esHost, + EsPort: *esPort, + UDPPort: *udpPort, + DisableEs: *disableEs, + PrometheusPort: *prometheusPort, + EsIndex: *esIndex, + Debug: *debug, + RefreshDelta: *refreshDelta, + CacheTTL: *cacheTTL, + PeerFile: *peerFile, + Country: *country, + StartPeerAdder: *startPeerAdder, + StartPrometheus: *startPrometheus, + StartUDP: *startUdp, + WritePeers: *writePeers, + } + + if esHost, ok := environment["ELASTIC_HOST"]; ok { + args.EsHost = esHost + } + + if !strings.HasPrefix(args.EsHost, "http") { + args.EsHost = "http://" + args.EsHost + } + + if esPort, ok := environment["ELASTIC_PORT"]; ok { + args.EsPort = esPort + } + + if prometheusPort, ok := environment["GOHUB_PROMETHEUS_PORT"]; ok { + args.PrometheusPort = prometheusPort + } + + /* + Verify no invalid argument combinations + */ + if len(*channelIds) > 0 && *channelId != "" { + log.Fatal("Cannot specify both channel_id and channel_ids") + } + + if serveCmd.Happened() { + args.CmdType = ServeCmd + } else if searchCmd.Happened() { + args.CmdType = SearchCmd + } + + if *text != "" { + searchRequest.Text = *text + } + if *name != "" { + searchRequest.ClaimName = *name + } + if *claimType != "" { + searchRequest.ClaimType = []string{*claimType} + } + if *id != "" { + searchRequest.ClaimId = &pb.InvertibleField{Invert: false, Value: []string{*id}} + } + if *author != "" { + searchRequest.Author = *author + } + if *title != "" { + searchRequest.Title = *title + } + if *description != "" { + searchRequest.Description = *description + } + if *channelId != "" { + searchRequest.ChannelId = &pb.InvertibleField{Invert: false, Value: []string{*channelId}} + } + if len(*channelIds) > 0 { + searchRequest.ChannelId = &pb.InvertibleField{Invert: false, Value: *channelIds} + } + + return args +} \ No newline at end of file diff --git a/server/federation.go b/server/federation.go new file mode 100644 index 0000000..ac69fa2 --- /dev/null +++ b/server/federation.go @@ -0,0 +1,422 @@ +package server + +import ( + "bufio" + "context" + "log" + "math" + "os" + "strings" + "time" + + pb "github.com/lbryio/hub/protobuf/go" + "google.golang.org/grpc" +) + +// sub is an internal structure holding information about an open connection +// with a peer. +type sub struct { + stream pb.Hub_PeerSubscribeStreamingServer + done chan<- bool +} + +// peerAddMsg is an internal structure for use in the channel communicating +// to the peerAdder gorountine. +type peerAddMsg struct { + msg *pb.ServerMessage + ping bool +} + +// FederatedServer hold relevant information about peers that we known about. +type FederatedServer struct { + Address string + Port string + Ts time.Time +} + +// peerKey takes a ServerMessage object and returns the key that for that peer +// in our peer table. +func peerKey(msg *pb.ServerMessage) string { + return msg.Address + ":" + msg.Port +} + +// loadPeers takes the arguments given to the hub at startup and loads the +// previously known peers from disk and verifies their existence before +// storing them as known peers. Returns a map of peerKey -> object +func loadPeers(args *Args) map[string]*FederatedServer { + localHosts := map[string]bool { + "127.0.0.1": true, + "0.0.0.0": true, + "localhost": true, + } + servers := make(map[string]*FederatedServer) + peerFile := args.PeerFile + port := args.Port + + f, err := os.Open(peerFile) + if err != nil { + log.Println(err) + return map[string]*FederatedServer{} + } + scanner := bufio.NewScanner(f) + scanner.Split(bufio.ScanLines) + var text []string + for scanner.Scan() { + text = append(text, scanner.Text()) + } + err = f.Close() + if err != nil { + log.Println("peer file failed to close: ", err) + } + + for _, line := range text { + ipPort := strings.Split(line,":") + if len(ipPort) != 2 { + log.Println("Malformed entry in peer file") + continue + } + // If the peer is us, skip + log.Println(args) + log.Println(ipPort) + if ipPort[1] == port && localHosts[ipPort[0]] { + log.Println("Self peer, skipping ...") + continue + } + server := &FederatedServer{ + Address: ipPort[0], + Port: ipPort[1], + Ts: time.Now(), + } + log.Println("pinging peer", server) + if helloPeer(server, args) { + servers[line] = server + } + } + + log.Println("Returning from loadPeers") + return servers +} + +// notifyPeer takes a peer to notify and a new peer we just learned about +// and calls AddPeer on the first. +func notifyPeer(peerToNotify *FederatedServer, newPeer *FederatedServer) error { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + + conn, err := grpc.DialContext(ctx, + peerToNotify.Address+":"+peerToNotify.Port, + grpc.WithInsecure(), + grpc.WithBlock(), + ) + if err != nil { + return err + } + defer conn.Close() + + msg := &pb.ServerMessage{ + Address: newPeer.Address, + Port: newPeer.Port, + } + + c := pb.NewHubClient(conn) + + _, err = c.AddPeer(ctx, msg) + if err != nil { + return err + } + + return nil +} + +// helloPeer takes a peer to say hello to and sends a hello message +// containing all the peers we know about and information about us. +// This is used to confirm existence of peers on start and let them +// know about us. Returns true is call was successful, false otherwise. +func helloPeer(server *FederatedServer, args *Args) bool { + log.Println("In helloPeer") + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + + conn, err := grpc.DialContext(ctx, + server.Address+":"+server.Port, + grpc.WithInsecure(), + grpc.WithBlock(), + ) + if err != nil { + log.Println(err) + return false + } + defer conn.Close() + + + c := pb.NewHubClient(conn) + + msg := &pb.HelloMessage{ + Port: args.Port, + Host: args.Host, + Servers: []*pb.ServerMessage{}, + } + res, err := c.Hello(ctx, msg) + if err != nil { + log.Println(err) + return false + } + + log.Println(res) + + return true +} + +// writePeers writes our current known peers to disk +// FIXME: This is probably inefficient, we just truncate the file and write +// the entire thing every time. Maybe use some sort of mmap? +func (s *Server) writePeers() { + if !s.Args.WritePeers { + return + } + failedCreat := "WARNING: Peer writer failed to create peer file, it's still running but may not be working!" + failedWrite := "WARNING: Peer writer failed to write a line, it's still running but may not be working!" + failedFlush := "WARNING: Peer writer failed to flush, it's still running but may not be working!" + failedClose := "WARNING: Peer writer failed to close the peer file, it's still running but may not be working!" + f, err := os.Create(s.Args.PeerFile) + if err != nil { + log.Println(failedCreat) + log.Println(err) + } + writer := bufio.NewWriter(f) + + for _, peer := range s.Servers { + line := peer.Address + ":" + peer.Port + "\n" + _, err := writer.WriteString(line) + if err != nil { + log.Println(failedWrite) + log.Println(err) + } + } + + err = writer.Flush() + if err != nil { + log.Println(failedFlush) + log.Println(err) + } + err = f.Close() + if err != nil { + log.Println(failedClose) + log.Println(err) + } +} + +// peerAdder is a goroutine which listens for new peers added and then +// optionally checks if they're online and adds them to our map of +// peers in a thread safe manner. +func (s *Server) peerAdder(ctx context.Context) { + for { + select { + case chanMsg := <-s.peerChannel: + msg := chanMsg.msg + ping := chanMsg.ping + + k := msg.Address + ":" + msg.Port + if _, ok := s.Servers[k]; !ok { + newServer := &FederatedServer{ + Address: msg.Address, + Port: msg.Port, + Ts: time.Now(), + } + log.Println(!ping) + if !ping || helloPeer(newServer, s.Args) { + s.Servers[k] = newServer + s.writePeers() + s.notifyPeerSubs(newServer) + } + } else { + s.Servers[k].Ts = time.Now() + } + case <-ctx.Done(): + log.Println("context finished, peerAdder shutting down.") + return + } + + } +} + +// getFastestPeer determines the fastest peer in its list of peers by sending +// out udp pings and seeing who responds first. This is currently not +// implemented. +func (s *Server) getFastestPeer() *FederatedServer { + log.Println(s.Servers) + if len(s.Servers) == 0 { + return nil + } + + for _, peer := range s.Servers { + return peer + } + + return nil +} + +// subscribeToPeer subscribes to a given peer hub in a streaming fashion. +func (s *Server) subscribeToPeer(peer *FederatedServer) { + var msg *pb.ServerMessage + + log.Println("Subscribing to peer: ", peer) + + if peer == nil { + return + } + + conn, err := grpc.Dial( + peer.Address+":"+peer.Port, + grpc.WithInsecure(), + grpc.WithBlock(), + ) + if err != nil { + log.Println(err) + return + } + defer conn.Close() + + + c := pb.NewHubClient(conn) + + var retries = 0 + var stream pb.Hub_PeerSubscribeStreamingClient + for retries <= 3 { + if stream == nil { + stream, err = c.PeerSubscribeStreaming( + context.Background(), + &pb.ServerMessage{Address: s.Args.Host, Port: s.Args.Port}, + ) + } + if err != nil { + goto retry + } + err = stream.RecvMsg(msg) + if err != nil { + goto retry + } + s.addPeer(msg, false) + continue + retry: + retries = retries + 1 + time.Sleep(time.Second * time.Duration(int(math.Pow(10, float64(retries))))) + } +} + +// subscribeToFastestPeer is a convenience function to find and subscribe to +// the fastest peer we know about in a streaming fashion. +func (s *Server) subscribeToFastestPeer(keepSubscribed bool) { + for { + peer := s.getFastestPeer() + s.subscribeToPeer(peer) + if !keepSubscribed { + return + } + // Put in a sleep, so we aren't looping like crazy if we have no peers + time.Sleep(time.Second * 5) + } +} + +// notifyPeerSubsStreaming notifies peer subs of new peers in a streaming +// fashion. +func (s *Server) notifyPeerSubsStreaming(newServer *FederatedServer) { + msg := &pb.ServerMessage{ + Address: newServer.Address, + Port: newServer.Port, + } + var unsubscribe []string + s.PeerSubs.Range(func(k, v interface{}) bool { + key, ok := k.(string) + if !ok { + log.Println("Failed to cast subscriber key: ", v) + return true + } + peer, ok := v.(sub) + if !ok { + log.Println("Failed to cast subscriber value: ", v) + return true + } + + log.Printf("Notifying peer %s of new node %+v\n", key, msg) + err := peer.stream.Send(msg) + if err != nil { + log.Println("Failed to send data to ", key) + select { + case peer.done <- true: + log.Println("Unsubscribed ", key) + default: + } + unsubscribe = append(unsubscribe, key) + } + return true + }) + + for _, key := range unsubscribe { + s.PeerSubs.Delete(key) + } +} + +func (s *Server) notifyPeerSubs(newServer *FederatedServer) { + var unsubscribe []string + s.PeerSubs.Range(func(k, v interface{}) bool { + key, ok := k.(string) + if !ok { + log.Println("Failed to cast subscriber key: ", v) + return true + } + peer, ok := v.(*FederatedServer) + if !ok { + log.Println("Failed to cast subscriber value: ", v) + return true + } + + log.Printf("Notifying peer %s of new node %+v\n", key, newServer) + err := notifyPeer(peer, newServer) + if err != nil { + log.Println("Failed to send data to ", key) + log.Println(err) + unsubscribe = append(unsubscribe, key) + } + return true + }) + + for _, key := range unsubscribe { + s.PeerSubs.Delete(key) + } +} + +// addPeer is an internal function to add a peer to this hub. +func (s *Server) addPeer(msg *pb.ServerMessage, ping bool) { + s.peerChannel <- &peerAddMsg{msg, ping} +} + +// mergeFederatedServers is an internal convenience function to add a list of +// peers. +func (s *Server) mergeFederatedServers(servers []*pb.ServerMessage) { + for _, srvMsg := range servers { + s.peerChannel <- &peerAddMsg{srvMsg, false} + } +} + +// makeHelloMessage makes a message for this hub to call the Hello endpoint +// on another hub. +func (s *Server) makeHelloMessage() *pb.HelloMessage { + n := len(s.Servers) + servers := make([]*pb.ServerMessage, n) + + var i = 0 + for _, v := range s.Servers { + servers[i] = &pb.ServerMessage{ + Address: v.Address, + Port: v.Port, + } + i += 1 + } + + return &pb.HelloMessage{ + Port: s.Args.Port, + Host: s.Args.Host, + Servers: servers, + } +} diff --git a/server/federation_test.go b/server/federation_test.go new file mode 100644 index 0000000..ac48639 --- /dev/null +++ b/server/federation_test.go @@ -0,0 +1,199 @@ +package server + +import ( + "bufio" + "context" + "fmt" + "log" + "os" + "strings" + "testing" + "time" + + pb "github.com/lbryio/hub/protobuf/go" +) + +// lineCountFile takes a fileName and counts the number of lines in it. +func lineCountFile(fileName string) int { + f, err := os.Open(fileName) + defer f.Close() + if err != nil { + log.Println(err) + return 0 + } + scanner := bufio.NewScanner(f) + scanner.Split(bufio.ScanLines) + var lineCount = 0 + for scanner.Scan() { + scanner.Text() + lineCount = lineCount + 1 + } + + return lineCount +} + +// removeFile removes a file. +func removeFile(fileName string) { + err := os.Remove(fileName) + if err != nil { + log.Println(err) + } +} + +// TestPeerAdder tests the peer adder goroutine. +func TestPeerAdder(t *testing.T) { + ctx := context.Background() + args := &Args{ + CmdType: ServeCmd, + Host: DefaultHost, + Port: DefaultPort, + EsHost: DefaultEsHost, + EsPort: DefaultEsPort, + UDPPort: DefaultUdpPort, + DisableEs: true, + PrometheusPort: DefaultPrometheusPort, + EsIndex: DefaultEsIndex, + Debug: true, + RefreshDelta: DefaultRefreshDelta, + CacheTTL: DefaultCacheTTL, + PeerFile: DefaultPeerFile, + Country: DefaultCountry, + StartPeerAdder: false, + StartPrometheus: false, + StartUDP: false, + WritePeers: false, + } + + tests := []struct { + name string + want int + } { + { + name: "Add 10 peers", + want: 10, + }, + { + name: "Add 10 peers, 1 unique", + want: 1, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T){ + server := MakeHubServer(ctx, args) + ctxWCancel, cancel := context.WithCancel(ctx) + + go server.peerAdder(ctxWCancel) + for i := 0; i < 10; i++ { + var msg *peerAddMsg + if strings.Contains(tt.name, "1 unique") { + msg = &peerAddMsg{ + msg: &pb.ServerMessage{ + Address: "1.1.1.1", + Port: "50051", + }, + ping: false, + } + } else { + msg = &peerAddMsg{ + msg: &pb.ServerMessage{ + Address: fmt.Sprintf("%d.%d.%d.%d", i, i, i, i), + Port: "50051", + }, + ping: false, + } + } + server.peerChannel <- msg + } + // Have to give it a second to update peers since it's in + // another thread. + time.Sleep(time.Second) + got := len(server.Servers) + if got != tt.want { + t.Errorf("len(server.Servers) = %d, want %d", got, tt.want) + } + cancel() + }) + } + +} + +// TestPeerWriter tests that the peerAdder goroutine writes the peer file +// properly when set to do so. +func TestPeerWriter(t *testing.T) { + ctx := context.Background() + args := &Args{ + CmdType: ServeCmd, + Host: DefaultHost, + Port: DefaultPort, + EsHost: DefaultEsHost, + EsPort: DefaultEsPort, + UDPPort: DefaultUdpPort, + DisableEs: true, + PrometheusPort: DefaultPrometheusPort, + EsIndex: DefaultEsIndex, + Debug: true, + RefreshDelta: DefaultRefreshDelta, + CacheTTL: DefaultCacheTTL, + PeerFile: DefaultPeerFile, + Country: DefaultCountry, + StartPeerAdder: false, + StartPrometheus: false, + StartUDP: false, + WritePeers: true, + } + + tests := []struct { + name string + want int + } { + { + name: "Add 10 peers", + want: 10, + }, + { + name: "Add 10 peers, 1 unique", + want: 1, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T){ + server := MakeHubServer(ctx, args) + ctxWCancel, cancel := context.WithCancel(ctx) + + go server.peerAdder(ctxWCancel) + for i := 0; i < 10; i++ { + var msg *peerAddMsg + if strings.Contains(tt.name, "1 unique") { + msg = &peerAddMsg{ + msg: &pb.ServerMessage{ + Address: "1.1.1.1", + Port: "50051", + }, + ping: false, + } + } else { + msg = &peerAddMsg{ + msg: &pb.ServerMessage{ + Address: fmt.Sprintf("%d.%d.%d.%d", i, i, i, i), + Port: "50051", + }, + ping: false, + } + } + server.peerChannel <- msg + } + // Have to give it a second to update peers since it's in + // another thread. + time.Sleep(time.Second * 1) + got := lineCountFile(server.Args.PeerFile) + if got != tt.want { + t.Errorf("len(server.Servers) = %d, want %d", got, tt.want) + } + cancel() + }) + } + + removeFile(args.PeerFile) +} diff --git a/server/search.go b/server/search.go index 44b754d..e0fd617 100644 --- a/server/search.go +++ b/server/search.go @@ -11,8 +11,6 @@ import ( "strings" "time" - //"github.com/lbryio/hub/schema" - "github.com/lbryio/hub/internal/metrics" pb "github.com/lbryio/hub/protobuf/go" "github.com/lbryio/lbry.go/v2/extras/util" @@ -24,8 +22,11 @@ import ( "gopkg.in/karalabe/cookiejar.v1/collections/deque" ) +// DefaultSearchSize is the default max number of items an +// es search will return. const DefaultSearchSize = 1000 +// record is a struct for the response from es. type record struct { Txid string `json:"tx_id"` Nout uint32 `json:"tx_nout"` @@ -50,11 +51,14 @@ type record struct { ClaimName string `json:"claim_name"` } +// orderField is struct for specifying ordering of es search results. type orderField struct { Field string IsAsc bool } +// StrArrToInterface takes an array of strings and returns them as an array of +// interfaces. func StrArrToInterface(arr []string) []interface{} { searchVals := make([]interface{}, len(arr)) for i := 0; i < len(arr); i++ { @@ -63,6 +67,9 @@ func StrArrToInterface(arr []string) []interface{} { return searchVals } +// AddTermsField takes an es bool query, array of string values and a term +// name and adds a TermsQuery for that name matching those values to the +// bool query. func AddTermsField(q *elastic.BoolQuery, arr []string, name string) *elastic.BoolQuery { if len(arr) == 0 { return q @@ -71,6 +78,9 @@ func AddTermsField(q *elastic.BoolQuery, arr []string, name string) *elastic.Boo return q.Must(elastic.NewTermsQuery(name, searchVals...)) } +// AddTermField takes an es bool query, a string value and a term name +// and adds a TermQuery for that name matching that value to the bool +// query. func AddTermField(q *elastic.BoolQuery, value string, name string) *elastic.BoolQuery { if value != "" { return q.Must(elastic.NewTermQuery(name, value)) @@ -78,6 +88,9 @@ func AddTermField(q *elastic.BoolQuery, value string, name string) *elastic.Bool return q } +// AddIndividualTermFields takes a bool query, an array of string values +// a term name, and a bool to invert the query, and adds multiple individual +// TermQuerys for that name matching each of the values. func AddIndividualTermFields(q *elastic.BoolQuery, arr []string, name string, invert bool) *elastic.BoolQuery { for _, x := range arr { if invert { @@ -89,6 +102,8 @@ func AddIndividualTermFields(q *elastic.BoolQuery, arr []string, name string, in return q } +// AddRangeField takes a bool query, a range field struct and a term name +// and adds a term query for that name matching that range field. func AddRangeField(q *elastic.BoolQuery, rq *pb.RangeField, name string) *elastic.BoolQuery { if rq == nil { return q @@ -112,6 +127,8 @@ func AddRangeField(q *elastic.BoolQuery, rq *pb.RangeField, name string) *elasti } } +// AddInvertibleField takes a bool query, an invertible field and a term name +// and adds a term query for that name matching that invertible field. func AddInvertibleField(q *elastic.BoolQuery, field *pb.InvertibleField, name string) *elastic.BoolQuery { if field == nil { return q @@ -128,11 +145,16 @@ func AddInvertibleField(q *elastic.BoolQuery, field *pb.InvertibleField, name st } } +// recordErrorAndDie is for fatal errors. It takes an error, increments the +// fatal error metric in prometheus and prints a fatal error message. func (s *Server) recordErrorAndDie(err error) { metrics.ErrorsCounter.With(prometheus.Labels{"error_type": "fatal"}).Inc() log.Fatalln(err) } +// RoundUpReleaseTime take a bool query, a range query and a term name +// and adds a term query for that name (this is for the release time +// field) with the value rounded up. func RoundUpReleaseTime(q *elastic.BoolQuery, rq *pb.RangeField, name string) *elastic.BoolQuery { if rq == nil { return q @@ -170,6 +192,11 @@ func RoundUpReleaseTime(q *elastic.BoolQuery, rq *pb.RangeField, name string) *e // 8) return streams referenced by repost and all channel referenced in extra_txos //*/ func (s *Server) Search(ctx context.Context, in *pb.SearchRequest) (*pb.Outputs, error) { + if s.Args.DisableEs { + log.Println("ElasticSearch disable, return nil to search") + return nil, nil + } + metrics.RequestsCount.With(prometheus.Labels{"method": "search"}).Inc() defer func(t time.Time) { delta := time.Since(t).Seconds() @@ -301,6 +328,7 @@ func (s *Server) Search(ctx context.Context, in *pb.SearchRequest) (*pb.Outputs, }, nil } +// normalizeTag takes a string and normalizes it for search in es. func (s *Server) normalizeTag(tag string) string { c := cases.Lower(language.English) res := s.MultiSpaceRe.ReplaceAll( @@ -312,6 +340,7 @@ func (s *Server) normalizeTag(tag string) string { return string(res) } +// cleanTags takes an array of tags and normalizes them. func (s *Server) cleanTags(tags []string) []string { cleanedTags := make([]string, len(tags)) for i, tag := range tags { @@ -320,6 +349,8 @@ func (s *Server) cleanTags(tags []string) []string { return cleanedTags } +// searchResultToRecords takes an elastic.SearchResult object and converts +// them to internal record structures. func (s *Server) searchResultToRecords( searchResult *elastic.SearchResult) []*record { records := make([]*record, 0, searchResult.TotalHits()) @@ -334,6 +365,9 @@ func (s *Server) searchResultToRecords( return records } +// postProcessResults takes es search result records and runs our +// post processing on them. +// TODO: more in depth description. func (s *Server) postProcessResults( ctx context.Context, client *elastic.Client, @@ -397,6 +431,8 @@ func (s *Server) postProcessResults( return txos, extraTxos, blocked } +// checkQuery takes a search request and does a sanity check on it for +// validity. func (s *Server) checkQuery(in *pb.SearchRequest) error { limit := 2048 checks := map[string]bool{ @@ -418,6 +454,8 @@ func (s *Server) checkQuery(in *pb.SearchRequest) error { return nil } +// setPageVars takes a search request and pointers to the local pageSize +// and from variables and sets them from the struct. func setPageVars(in *pb.SearchRequest, pageSize *int, from *int) { if in.Limit > 0 { log.Printf("############ limit: %d\n", in.Limit) @@ -429,6 +467,8 @@ func setPageVars(in *pb.SearchRequest, pageSize *int, from *int) { } } +// setupEsQuery takes an elastic.BoolQuery, pb.SearchRequest and orderField +// and adds the search request terms to the bool query. func (s *Server) setupEsQuery( q *elastic.BoolQuery, in *pb.SearchRequest, @@ -625,6 +665,8 @@ func (s *Server) setupEsQuery( return q } +// getUniqueChannels takes the record results from the es search and returns +// the unique channels from those records as a list and a map. func (s *Server) getUniqueChannels(records []*record, client *elastic.Client, ctx context.Context, searchIndices []string) ([]*pb.Output, map[string]*pb.Output) { channels := make(map[string]*pb.Output) channelsSet := make(map[string]bool) @@ -678,6 +720,9 @@ func (s *Server) getUniqueChannels(records []*record, client *elastic.Client, ct return channelTxos, channels } +// getClaimsForReposts takes the record results from the es query and returns +// an array and map of the reposted records as well as an array of those +// records. func (s *Server) getClaimsForReposts(ctx context.Context, client *elastic.Client, records []*record, searchIndices []string) ([]*pb.Output, []*record, map[string]*pb.Output) { var totalReposted = 0 @@ -731,10 +776,8 @@ func (s *Server) getClaimsForReposts(ctx context.Context, client *elastic.Client return claims, repostedRecords, respostedMap } -/* - Takes a search request and serializes into a string for use as a key into the - internal cache for the hub. -*/ +// serializeSearchRequest takes a search request and serializes it into a key +// for use in the internal cache for the hub. func (s *Server) serializeSearchRequest(request *pb.SearchRequest) string { // Save the offest / limit and set to zero, cache hits should happen regardless // and they're used in post processing @@ -754,6 +797,8 @@ func (s *Server) serializeSearchRequest(request *pb.SearchRequest) string { return str } +// searchAhead takes an array of record results, the pageSize and +// perChannelPerPage value and returns the hits for this page. func searchAhead(searchHits []*record, pageSize int, perChannelPerPage int) []*record { finalHits := make([]*record, 0, len(searchHits)) var channelCounters map[string]int @@ -796,6 +841,8 @@ func searchAhead(searchHits []*record, pageSize int, perChannelPerPage int) []*r return finalHits } +// recordToOutput is a function on a record struct to turn it into a pb.Output +// struct. func (r *record) recordToOutput() *pb.Output { return &pb.Output{ TxHash: util.TxIdToTxHash(r.Txid), @@ -822,6 +869,8 @@ func (r *record) recordToOutput() *pb.Output { } } +// getHitId is a function on the record struct to get the id for the search +// hit. func (r *record) getHitId() string { if r.RepostedClaimId != "" { return r.RepostedClaimId @@ -830,6 +879,7 @@ func (r *record) getHitId() string { } } +// removeDuplicates takes an array of record results and remove duplicates. func removeDuplicates(searchHits []*record) []*record { dropped := make(map[*record]bool) // claim_id -> (creation_height, hit_id), where hit_id is either reposted claim id or original @@ -865,6 +915,8 @@ func removeDuplicates(searchHits []*record) []*record { return deduped } +// removeBlocked takes an array of record results from the es search +// and removes blocked records. func removeBlocked(searchHits []*record) ([]*record, []*record, map[string]*pb.Blocked) { newHits := make([]*record, 0, len(searchHits)) blockedHits := make([]*record, 0, len(searchHits)) diff --git a/server/server.go b/server/server.go index b73a6ec..796ff78 100644 --- a/server/server.go +++ b/server/server.go @@ -9,6 +9,7 @@ import ( "net/http" "os" "regexp" + "sync" "time" "github.com/ReneKroon/ttlcache/v2" @@ -27,39 +28,17 @@ type Server struct { MultiSpaceRe *regexp.Regexp WeirdCharsRe *regexp.Regexp EsClient *elastic.Client - Servers []*FederatedServer + Servers map[string]*FederatedServer QueryCache *ttlcache.Cache S256 *hash.Hash LastRefreshCheck time.Time RefreshDelta time.Duration NumESRefreshes int64 + PeerSubs sync.Map + peerChannel chan *peerAddMsg pb.UnimplementedHubServer } -type FederatedServer struct { - Address string - Port string - Ts time.Time - Ping int //? -} - -const ( - ServeCmd = iota - SearchCmd = iota -) - -type Args struct { - // TODO Make command types an enum - CmdType int - Host string - Port string - EsHost string - EsPort string - EsIndex string - Debug bool - RefreshDelta int - CacheTTL int -} func getVersion() string { return meta.Version @@ -104,9 +83,14 @@ func getVersion() string { 'blockchain.address.unsubscribe' */ -func MakeHubServer(args *Args) *Server { +// MakeHubServer takes the arguments given to a hub when it's started and +// initializes everything. It loads information about previously known peers, +// creates needed internal data structures, and initializes goroutines. +func MakeHubServer(ctx context.Context, args *Args) *Server { grpcServer := grpc.NewServer(grpc.NumStreamWorkers(10)) + peerChannel := make(chan *peerAddMsg) + multiSpaceRe, err := regexp.Compile(`\s{2,}`) if err != nil { log.Fatal(err) @@ -116,23 +100,27 @@ func MakeHubServer(args *Args) *Server { if err != nil { log.Fatal(err) } - self := &FederatedServer{ - Address: "127.0.0.1", - Port: args.Port, - Ts: time.Now(), - Ping: 0, - } - servers := make([]*FederatedServer, 10) - servers = append(servers, self) - esUrl := args.EsHost + ":" + args.EsPort - opts := []elastic.ClientOptionFunc{elastic.SetSniff(false), elastic.SetURL(esUrl)} - if args.Debug { - opts = append(opts, elastic.SetTraceLog(log.New(os.Stderr, "[[ELASTIC]]", 0))) - } - client, err := elastic.NewClient(opts...) - if err != nil { - log.Fatal(err) + servers := loadPeers(args) + + var client *elastic.Client + if !args.DisableEs { + esUrl := args.EsHost + ":" + args.EsPort + opts := []elastic.ClientOptionFunc{ + elastic.SetSniff(true), + elastic.SetSnifferTimeoutStartup(time.Second * 60), + elastic.SetSnifferTimeout(time.Second * 60), + elastic.SetURL(esUrl), + } + if args.Debug { + opts = append(opts, elastic.SetTraceLog(log.New(os.Stderr, "[[ELASTIC]]", 0))) + } + client, err = elastic.NewClient(opts...) + if err != nil { + log.Fatal(err) + } + } else { + client = nil } cache := ttlcache.NewCache() @@ -157,34 +145,124 @@ func MakeHubServer(args *Args) *Server { LastRefreshCheck: time.Now(), RefreshDelta: refreshDelta, NumESRefreshes: 0, + Servers: servers, + PeerSubs: sync.Map{}, + peerChannel: peerChannel, + } + + // Start up our background services + if args.StartPeerAdder { + go s.peerAdder(ctx) + } + if args.StartPrometheus { + go s.prometheusEndpoint(s.Args.PrometheusPort, "metrics") + } + if args.StartUDP { + go func() { + err := UDPServer(args) + if err != nil { + log.Println("UDP Server failed!", err) + } + }() } return s } -func (s *Server) PromethusEndpoint(port string, endpoint string) error { +// prometheusEndpoint is a goroutine which start up a prometheus endpoint +// for this hub to allow for metric tracking. +func (s *Server) prometheusEndpoint(port string, endpoint string) { http.Handle("/"+endpoint, promhttp.Handler()) log.Println(fmt.Sprintf("listening on :%s /%s", port, endpoint)) err := http.ListenAndServe(":"+port, nil) - if err != nil { - return err + log.Fatalln("Shouldn't happen??!?!", err) +} + +// Hello is a grpc endpoint to allow another hub to tell us about itself. +// The passed message includes information about the other hub, and all +// of its peers which are added to the knowledge of this hub. +func (s *Server) Hello(ctx context.Context, args *pb.HelloMessage) (*pb.HelloMessage, error) { + port := args.Port + host := args.Host + server := &FederatedServer{ + Address: host, + Port: port, + Ts: time.Now(), } - log.Fatalln("Shouldn't happen??!?!") - return nil + log.Println(server) + + s.addPeer(&pb.ServerMessage{Address: host, Port: port}, false) + s.mergeFederatedServers(args.Servers) + s.writePeers() + s.notifyPeerSubs(server) + + return s.makeHelloMessage(), nil } -func (s *Server) Hello(context context.Context, args *FederatedServer) (*FederatedServer, error) { - s.Servers = append(s.Servers, args) +// PeerSubscribe adds a peer hub to the list of subscribers to update about +// new peers. +func (s *Server) PeerSubscribe(ctx context.Context, in *pb.ServerMessage) (*pb.StringValue, error) { + peer := &FederatedServer{ + Address: in.Address, + Port: in.Port, + Ts: time.Now(), + } - return s.Servers[0], nil + s.PeerSubs.Store(peerKey(in), peer) + + return &pb.StringValue{Value: "Success"}, nil } -func (s *Server) Ping(context context.Context, args *pb.EmptyMessage) (*pb.StringValue, error) { +// PeerSubscribeStreaming is a streaming grpc endpoint that allows another hub to +// subscribe to this hub for peer updates. This first loops through all the +// peers that this hub knows about and sends them to the connecting hub, +// it then stores the peer, along with a channel to indicate when it's finished +// and it's stream context in our map of peer subs. Finally, it waits on the +// context to finish, or a message in the done channel. This function cannot +// exit while the peer is subscribe or the context will die. Communicating with +// the peer is handled by the peerAdder goroutine in federation.go, which +// listens on a channel of new peers and notifies all our connected peers when +// we find out about a new one. +func (s *Server) PeerSubscribeStreaming(in *pb.ServerMessage, stream pb.Hub_PeerSubscribeStreamingServer) error { + for _, server := range s.Servers { + err := stream.Send(&pb.ServerMessage{ + Address: server.Address, + Port: server.Port, + }) + if err != nil { + return err + } + } + + done := make(chan bool) + ctx := stream.Context() + s.PeerSubs.Store(peerKey(in), &sub{stream: stream, done: done}) + for { + select { + case <-done: + log.Println("Removing client:", in) + return nil + case <-ctx.Done(): + log.Println("Client disconnected: ", in) + return nil + } + } +} + +// AddPeer is a grpc endpoint to tell this hub about another hub in the network. +func (s *Server) AddPeer(ctx context.Context, args *pb.ServerMessage) (*pb.StringValue, error) { + s.addPeer(args, true) + return &pb.StringValue{Value: "Success!"}, nil +} + +// Ping is a grpc endpoint that returns a short message. +func (s *Server) Ping(ctx context.Context, args *pb.EmptyMessage) (*pb.StringValue, error) { metrics.RequestsCount.With(prometheus.Labels{"method": "ping"}).Inc() return &pb.StringValue{Value: "Hello, world!"}, nil } -func (s *Server) Version(context context.Context, args *pb.EmptyMessage) (*pb.StringValue, error) { +// Version is a grpc endpoint to get this hub's version. +func (s *Server) Version(ctx context.Context, args *pb.EmptyMessage) (*pb.StringValue, error) { metrics.RequestsCount.With(prometheus.Labels{"method": "version"}).Inc() return &pb.StringValue{Value: getVersion()}, nil } diff --git a/server/udp.go b/server/udp.go new file mode 100644 index 0000000..bf8ad5f --- /dev/null +++ b/server/udp.go @@ -0,0 +1,226 @@ +package server + +import ( + "encoding/binary" + "fmt" + "net" + "strconv" + "strings" + "time" + + "github.com/lbryio/lbry.go/v2/extras/errors" +) + +const maxBufferSize = 1024 +// genesis blocktime (which is actually wrong) +const magic = 1446058291 +const protocolVersion = 1 + +// SPVPing is a struct for the format of how to ping another hub over udp. +// format b'!lB64s' +type SPVPing struct { + magic uint32 + version byte + padding []byte //64 +} + +// SPVPong is a struct for the return pong from another hub server. +// format b'!BBL32s4sH' +type SPVPong struct { + protocolVersion byte + flags byte + height uint32 + tip []byte // 32 + srcAddrRaw []byte // 4 + country uint16 +} + +// encodeSPVPing creates a slice of bytes to ping another hub with +// over udp. +func encodeSPVPing() []byte { + data := make([]byte, 69) + + binary.BigEndian.PutUint32(data, magic) + data[4] = protocolVersion + + return data +} + +// decodeSPVPing takes a slice of bytes and decodes an SPVPing struct from them. +func decodeSPVPing(data []byte) *SPVPing { + if len(data) < 69 { + return nil + } + + parsedMagic := binary.BigEndian.Uint32(data) + parsedProtocalVersion := data[4] + return &SPVPing{ + magic: parsedMagic, + version: parsedProtocalVersion, + } +} + +// Encode is a function for SPVPong structs to encode them into bytes for +// sending over udp. +func (pong *SPVPong) Encode() []byte { + data := make([]byte, 44) + + data[0] = pong.protocolVersion + data[1] = pong.flags + binary.BigEndian.PutUint32(data[2:], pong.height) + copy(data[6:], pong.tip) + copy(data[38:], pong.srcAddrRaw) + binary.BigEndian.PutUint16(data[42:], pong.country) + + return data +} + +// makeSPVPong creates an SPVPong struct according to given parameters. +// FIXME: Currently, does not correctly encode the country. +func makeSPVPong(flags int, height int, tip []byte, sourceAddr string, country string) *SPVPong { + byteAddr := EncodeAddress(sourceAddr) + countryInt := 1 + return &SPVPong{ + protocolVersion: protocolVersion, + flags: byte(flags), + height: uint32(height), + tip: tip, + srcAddrRaw: byteAddr, + country: uint16(countryInt), + } +} + +// decodeSPVPong takes a slice of bytes and decodes an SPVPong struct +// from it. +func decodeSPVPong(data []byte) *SPVPong { + if len(data) < 44 { + return nil + } + + parsedProtocalVersion := data[0] + flags := data[1] + height := binary.BigEndian.Uint32(data[:2]) + tip := make([]byte, 32) + copy(tip, data[6:38]) + srcRawAddr := make([]byte, 4) + copy(srcRawAddr, data[38:42]) + country := binary.BigEndian.Uint16(data[:42]) + return &SPVPong{ + protocolVersion: parsedProtocalVersion, + flags: flags, + height: height, + tip: tip, + srcAddrRaw: srcRawAddr, + country: country, + } +} + +// EncodeAddress takes an ipv4 address and encodes it into bytes for the hub +// Ping/Pong protocol. +func EncodeAddress(addr string) []byte { + parts := strings.Split(addr, ".") + + if len(parts) != 4 { + return []byte{} + } + + data := make([]byte, 4) + for i, part := range parts { + x, err := strconv.Atoi(part) + if err != nil || x > 255 { + return []byte{} + } + data[i] = byte(x) + } + + return data +} + +// DecodeAddress gets the string ipv4 address from an SPVPong struct. +func (pong *SPVPong) DecodeAddress() string { + return fmt.Sprintf("%d.%d.%d.%d", + pong.srcAddrRaw[0], + pong.srcAddrRaw[1], + pong.srcAddrRaw[2], + pong.srcAddrRaw[3], + ) +} + +// UDPPing sends a ping over udp to another hub and returns the ip address of +// this hub. +func UDPPing(address string) (string, error) { + addr, err := net.ResolveUDPAddr("udp", address) + if err != nil { + return "", err + } + + conn, err := net.DialUDP("udp", nil, addr) + if err != nil { + return "", err + } + + defer conn.Close() + + _, err = conn.Write(encodeSPVPing()) + if err != nil { + return "", err + } + + buffer := make([]byte, maxBufferSize) + deadline := time.Now().Add(time.Second) + err = conn.SetReadDeadline(deadline) + if err != nil { + return "", err + } + n, _, err := conn.ReadFromUDP(buffer) + if err != nil { + return "", err + } + + pong := decodeSPVPong(buffer[:n]) + + if pong == nil { + return "", errors.Base("Pong decoding failed") + } + + myAddr := pong.DecodeAddress() + + return myAddr, nil +} + +// UDPServer is a goroutine that starts an udp server that implements the hubs +// Ping/Pong protocol to find out about each other without making full TCP +// connections. +func UDPServer(args *Args) error { + address := ":" + args.UDPPort + tip := make([]byte, 32) + addr, err := net.ResolveUDPAddr("udp", address) + if err != nil { + return err + } + + conn, err := net.ListenUDP("udp", addr) + if err != nil { + return err + } + + defer conn.Close() + + buffer := make([]byte, maxBufferSize) + for { + //TODO verify ping + _, addr, err := conn.ReadFromUDP(buffer) + if err != nil { + return err + } + + sAddr := addr.IP.String() + pong := makeSPVPong(0,0, tip, sAddr, args.Country) + data := pong.Encode() + + _, err = conn.WriteToUDP(data, addr) + if err != nil { + return err + } + } +}