From 5387aeeebe56d807ac4e70a4652a349faa8995e2 Mon Sep 17 00:00:00 2001 From: Jeffrey Picard Date: Sun, 24 Oct 2021 21:39:37 -0400 Subject: [PATCH] Most of federation is written, need to finish udp and test Cleanup, more reorg, more arguments, started adding tests Comments and another test Simplify writing of peers and add unit test --- go.mod | 2 +- go.sum | 3 +- main.go | 165 +------- protobuf/definitions/hub.proto | 15 + protobuf/go/hub.pb.go | 671 +++++++++++++++++++++------------ protobuf/go/hub_grpc.pb.go | 174 ++++++++- scripts/version.py | 14 +- server/args.go | 200 ++++++++++ server/federation.go | 422 +++++++++++++++++++++ server/federation_test.go | 199 ++++++++++ server/search.go | 64 +++- server/server.go | 182 ++++++--- server/udp.go | 226 +++++++++++ 13 files changed, 1871 insertions(+), 466 deletions(-) create mode 100644 server/args.go create mode 100644 server/federation.go create mode 100644 server/federation_test.go create mode 100644 server/udp.go 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 + } + } +}