From 352df59cf3061f8d9ece05cd7b8cddc201f4a81e Mon Sep 17 00:00:00 2001 From: Volodymyr Manilo Date: Thu, 26 May 2022 10:32:51 +0200 Subject: [PATCH 1/7] WIP --- .gitignore | 1 + api/gen/powergate/user/v1/user.pb.go | 1738 +++++++++++++++--------- api/server/server.go | 5 +- api/server/user/storageconfig.go | 14 +- api/server/user/util.go | 203 ++- deals/module/module.go | 5 +- deals/module/store/store.go | 7 +- ffs/integrationtest/manager/manager.go | 4 +- ffs/types.go | 37 +- notifications/notifier.go | 31 + proto/powergate/user/v1/user.proto | 31 + 11 files changed, 1423 insertions(+), 653 deletions(-) create mode 100644 notifications/notifier.go diff --git a/.gitignore b/.gitignore index d486affc7..dffb3a5db 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,7 @@ cover.out # vscode config folder .vscode/ +.idea # File names that can be used for testing. myfile diff --git a/api/gen/powergate/user/v1/user.pb.go b/api/gen/powergate/user/v1/user.pb.go index 991e10cb8..6e262662e 100644 --- a/api/gen/powergate/user/v1/user.pb.go +++ b/api/gen/powergate/user/v1/user.pb.go @@ -3530,20 +3530,351 @@ func (x *ColdConfig) GetFilecoin() *FilConfig { return nil } +type WebhookAuthData struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Username string `protobuf:"bytes,1,opt,name=username,proto3" json:"username,omitempty"` + Password string `protobuf:"bytes,2,opt,name=password,proto3" json:"password,omitempty"` +} + +func (x *WebhookAuthData) Reset() { + *x = WebhookAuthData{} + if protoimpl.UnsafeEnabled { + mi := &file_powergate_user_v1_user_proto_msgTypes[65] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *WebhookAuthData) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WebhookAuthData) ProtoMessage() {} + +func (x *WebhookAuthData) ProtoReflect() protoreflect.Message { + mi := &file_powergate_user_v1_user_proto_msgTypes[65] + 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 WebhookAuthData.ProtoReflect.Descriptor instead. +func (*WebhookAuthData) Descriptor() ([]byte, []int) { + return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{65} +} + +func (x *WebhookAuthData) GetUsername() string { + if x != nil { + return x.Username + } + return "" +} + +func (x *WebhookAuthData) GetPassword() string { + if x != nil { + return x.Password + } + return "" +} + +type WebhookAuthentication struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` + Data *WebhookAuthData `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` +} + +func (x *WebhookAuthentication) Reset() { + *x = WebhookAuthentication{} + if protoimpl.UnsafeEnabled { + mi := &file_powergate_user_v1_user_proto_msgTypes[66] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *WebhookAuthentication) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WebhookAuthentication) ProtoMessage() {} + +func (x *WebhookAuthentication) ProtoReflect() protoreflect.Message { + mi := &file_powergate_user_v1_user_proto_msgTypes[66] + 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 WebhookAuthentication.ProtoReflect.Descriptor instead. +func (*WebhookAuthentication) Descriptor() ([]byte, []int) { + return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{66} +} + +func (x *WebhookAuthentication) GetType() string { + if x != nil { + return x.Type + } + return "" +} + +func (x *WebhookAuthentication) GetData() *WebhookAuthData { + if x != nil { + return x.Data + } + return nil +} + +type Webhook struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Endpoint string `protobuf:"bytes,1,opt,name=endpoint,proto3" json:"endpoint,omitempty"` + Authentication *WebhookAuthentication `protobuf:"bytes,2,opt,name=authentication,proto3" json:"authentication,omitempty"` +} + +func (x *Webhook) Reset() { + *x = Webhook{} + if protoimpl.UnsafeEnabled { + mi := &file_powergate_user_v1_user_proto_msgTypes[67] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Webhook) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Webhook) ProtoMessage() {} + +func (x *Webhook) ProtoReflect() protoreflect.Message { + mi := &file_powergate_user_v1_user_proto_msgTypes[67] + 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 Webhook.ProtoReflect.Descriptor instead. +func (*Webhook) Descriptor() ([]byte, []int) { + return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{67} +} + +func (x *Webhook) GetEndpoint() string { + if x != nil { + return x.Endpoint + } + return "" +} + +func (x *Webhook) GetAuthentication() *WebhookAuthentication { + if x != nil { + return x.Authentication + } + return nil +} + +type WebhookAlert struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` + Threshold string `protobuf:"bytes,2,opt,name=threshold,proto3" json:"threshold,omitempty"` +} + +func (x *WebhookAlert) Reset() { + *x = WebhookAlert{} + if protoimpl.UnsafeEnabled { + mi := &file_powergate_user_v1_user_proto_msgTypes[68] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *WebhookAlert) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WebhookAlert) ProtoMessage() {} + +func (x *WebhookAlert) ProtoReflect() protoreflect.Message { + mi := &file_powergate_user_v1_user_proto_msgTypes[68] + 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 WebhookAlert.ProtoReflect.Descriptor instead. +func (*WebhookAlert) Descriptor() ([]byte, []int) { + return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{68} +} + +func (x *WebhookAlert) GetType() string { + if x != nil { + return x.Type + } + return "" +} + +func (x *WebhookAlert) GetThreshold() string { + if x != nil { + return x.Threshold + } + return "" +} + +type WebhookConfiguration struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Events []string `protobuf:"bytes,1,rep,name=events,proto3" json:"events,omitempty"` + Alerts []*WebhookAlert `protobuf:"bytes,2,rep,name=alerts,proto3" json:"alerts,omitempty"` +} + +func (x *WebhookConfiguration) Reset() { + *x = WebhookConfiguration{} + if protoimpl.UnsafeEnabled { + mi := &file_powergate_user_v1_user_proto_msgTypes[69] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *WebhookConfiguration) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WebhookConfiguration) ProtoMessage() {} + +func (x *WebhookConfiguration) ProtoReflect() protoreflect.Message { + mi := &file_powergate_user_v1_user_proto_msgTypes[69] + 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 WebhookConfiguration.ProtoReflect.Descriptor instead. +func (*WebhookConfiguration) Descriptor() ([]byte, []int) { + return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{69} +} + +func (x *WebhookConfiguration) GetEvents() []string { + if x != nil { + return x.Events + } + return nil +} + +func (x *WebhookConfiguration) GetAlerts() []*WebhookAlert { + if x != nil { + return x.Alerts + } + return nil +} + +type NotificationConfig struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Webhook *Webhook `protobuf:"bytes,1,opt,name=webhook,proto3" json:"webhook,omitempty"` + Configuration *WebhookConfiguration `protobuf:"bytes,2,opt,name=configuration,proto3" json:"configuration,omitempty"` +} + +func (x *NotificationConfig) Reset() { + *x = NotificationConfig{} + if protoimpl.UnsafeEnabled { + mi := &file_powergate_user_v1_user_proto_msgTypes[70] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *NotificationConfig) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NotificationConfig) ProtoMessage() {} + +func (x *NotificationConfig) ProtoReflect() protoreflect.Message { + mi := &file_powergate_user_v1_user_proto_msgTypes[70] + 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 NotificationConfig.ProtoReflect.Descriptor instead. +func (*NotificationConfig) Descriptor() ([]byte, []int) { + return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{70} +} + +func (x *NotificationConfig) GetWebhook() *Webhook { + if x != nil { + return x.Webhook + } + return nil +} + +func (x *NotificationConfig) GetConfiguration() *WebhookConfiguration { + if x != nil { + return x.Configuration + } + return nil +} + type StorageConfig struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Hot *HotConfig `protobuf:"bytes,1,opt,name=hot,proto3" json:"hot,omitempty"` - Cold *ColdConfig `protobuf:"bytes,2,opt,name=cold,proto3" json:"cold,omitempty"` - Repairable bool `protobuf:"varint,3,opt,name=repairable,proto3" json:"repairable,omitempty"` + Hot *HotConfig `protobuf:"bytes,1,opt,name=hot,proto3" json:"hot,omitempty"` + Cold *ColdConfig `protobuf:"bytes,2,opt,name=cold,proto3" json:"cold,omitempty"` + Repairable bool `protobuf:"varint,3,opt,name=repairable,proto3" json:"repairable,omitempty"` + Notifications []*NotificationConfig `protobuf:"bytes,4,rep,name=notifications,proto3" json:"notifications,omitempty"` } func (x *StorageConfig) Reset() { *x = StorageConfig{} if protoimpl.UnsafeEnabled { - mi := &file_powergate_user_v1_user_proto_msgTypes[65] + mi := &file_powergate_user_v1_user_proto_msgTypes[71] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3556,7 +3887,7 @@ func (x *StorageConfig) String() string { func (*StorageConfig) ProtoMessage() {} func (x *StorageConfig) ProtoReflect() protoreflect.Message { - mi := &file_powergate_user_v1_user_proto_msgTypes[65] + mi := &file_powergate_user_v1_user_proto_msgTypes[71] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3569,7 +3900,7 @@ func (x *StorageConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use StorageConfig.ProtoReflect.Descriptor instead. func (*StorageConfig) Descriptor() ([]byte, []int) { - return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{65} + return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{71} } func (x *StorageConfig) GetHot() *HotConfig { @@ -3593,6 +3924,13 @@ func (x *StorageConfig) GetRepairable() bool { return false } +func (x *StorageConfig) GetNotifications() []*NotificationConfig { + if x != nil { + return x.Notifications + } + return nil +} + type IpfsHotInfo struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -3604,7 +3942,7 @@ type IpfsHotInfo struct { func (x *IpfsHotInfo) Reset() { *x = IpfsHotInfo{} if protoimpl.UnsafeEnabled { - mi := &file_powergate_user_v1_user_proto_msgTypes[66] + mi := &file_powergate_user_v1_user_proto_msgTypes[72] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3617,7 +3955,7 @@ func (x *IpfsHotInfo) String() string { func (*IpfsHotInfo) ProtoMessage() {} func (x *IpfsHotInfo) ProtoReflect() protoreflect.Message { - mi := &file_powergate_user_v1_user_proto_msgTypes[66] + mi := &file_powergate_user_v1_user_proto_msgTypes[72] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3630,7 +3968,7 @@ func (x *IpfsHotInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use IpfsHotInfo.ProtoReflect.Descriptor instead. func (*IpfsHotInfo) Descriptor() ([]byte, []int) { - return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{66} + return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{72} } func (x *IpfsHotInfo) GetCreated() int64 { @@ -3653,7 +3991,7 @@ type HotInfo struct { func (x *HotInfo) Reset() { *x = HotInfo{} if protoimpl.UnsafeEnabled { - mi := &file_powergate_user_v1_user_proto_msgTypes[67] + mi := &file_powergate_user_v1_user_proto_msgTypes[73] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3666,7 +4004,7 @@ func (x *HotInfo) String() string { func (*HotInfo) ProtoMessage() {} func (x *HotInfo) ProtoReflect() protoreflect.Message { - mi := &file_powergate_user_v1_user_proto_msgTypes[67] + mi := &file_powergate_user_v1_user_proto_msgTypes[73] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3679,7 +4017,7 @@ func (x *HotInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use HotInfo.ProtoReflect.Descriptor instead. func (*HotInfo) Descriptor() ([]byte, []int) { - return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{67} + return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{73} } func (x *HotInfo) GetEnabled() bool { @@ -3720,7 +4058,7 @@ type FilStorage struct { func (x *FilStorage) Reset() { *x = FilStorage{} if protoimpl.UnsafeEnabled { - mi := &file_powergate_user_v1_user_proto_msgTypes[68] + mi := &file_powergate_user_v1_user_proto_msgTypes[74] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3733,7 +4071,7 @@ func (x *FilStorage) String() string { func (*FilStorage) ProtoMessage() {} func (x *FilStorage) ProtoReflect() protoreflect.Message { - mi := &file_powergate_user_v1_user_proto_msgTypes[68] + mi := &file_powergate_user_v1_user_proto_msgTypes[74] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3746,7 +4084,7 @@ func (x *FilStorage) ProtoReflect() protoreflect.Message { // Deprecated: Use FilStorage.ProtoReflect.Descriptor instead. func (*FilStorage) Descriptor() ([]byte, []int) { - return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{68} + return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{74} } func (x *FilStorage) GetDealId() int64 { @@ -3811,7 +4149,7 @@ type FilInfo struct { func (x *FilInfo) Reset() { *x = FilInfo{} if protoimpl.UnsafeEnabled { - mi := &file_powergate_user_v1_user_proto_msgTypes[69] + mi := &file_powergate_user_v1_user_proto_msgTypes[75] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3824,7 +4162,7 @@ func (x *FilInfo) String() string { func (*FilInfo) ProtoMessage() {} func (x *FilInfo) ProtoReflect() protoreflect.Message { - mi := &file_powergate_user_v1_user_proto_msgTypes[69] + mi := &file_powergate_user_v1_user_proto_msgTypes[75] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3837,7 +4175,7 @@ func (x *FilInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use FilInfo.ProtoReflect.Descriptor instead. func (*FilInfo) Descriptor() ([]byte, []int) { - return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{69} + return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{75} } func (x *FilInfo) GetDataCid() string { @@ -3873,7 +4211,7 @@ type ColdInfo struct { func (x *ColdInfo) Reset() { *x = ColdInfo{} if protoimpl.UnsafeEnabled { - mi := &file_powergate_user_v1_user_proto_msgTypes[70] + mi := &file_powergate_user_v1_user_proto_msgTypes[76] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3886,7 +4224,7 @@ func (x *ColdInfo) String() string { func (*ColdInfo) ProtoMessage() {} func (x *ColdInfo) ProtoReflect() protoreflect.Message { - mi := &file_powergate_user_v1_user_proto_msgTypes[70] + mi := &file_powergate_user_v1_user_proto_msgTypes[76] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3899,7 +4237,7 @@ func (x *ColdInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use ColdInfo.ProtoReflect.Descriptor instead. func (*ColdInfo) Descriptor() ([]byte, []int) { - return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{70} + return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{76} } func (x *ColdInfo) GetEnabled() bool { @@ -3931,7 +4269,7 @@ type StorageInfo struct { func (x *StorageInfo) Reset() { *x = StorageInfo{} if protoimpl.UnsafeEnabled { - mi := &file_powergate_user_v1_user_proto_msgTypes[71] + mi := &file_powergate_user_v1_user_proto_msgTypes[77] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3944,7 +4282,7 @@ func (x *StorageInfo) String() string { func (*StorageInfo) ProtoMessage() {} func (x *StorageInfo) ProtoReflect() protoreflect.Message { - mi := &file_powergate_user_v1_user_proto_msgTypes[71] + mi := &file_powergate_user_v1_user_proto_msgTypes[77] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3957,7 +4295,7 @@ func (x *StorageInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use StorageInfo.ProtoReflect.Descriptor instead. func (*StorageInfo) Descriptor() ([]byte, []int) { - return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{71} + return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{77} } func (x *StorageInfo) GetJobId() string { @@ -4010,7 +4348,7 @@ type CidInfo struct { func (x *CidInfo) Reset() { *x = CidInfo{} if protoimpl.UnsafeEnabled { - mi := &file_powergate_user_v1_user_proto_msgTypes[72] + mi := &file_powergate_user_v1_user_proto_msgTypes[78] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4023,7 +4361,7 @@ func (x *CidInfo) String() string { func (*CidInfo) ProtoMessage() {} func (x *CidInfo) ProtoReflect() protoreflect.Message { - mi := &file_powergate_user_v1_user_proto_msgTypes[72] + mi := &file_powergate_user_v1_user_proto_msgTypes[78] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4036,7 +4374,7 @@ func (x *CidInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use CidInfo.ProtoReflect.Descriptor instead. func (*CidInfo) Descriptor() ([]byte, []int) { - return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{72} + return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{78} } func (x *CidInfo) GetCid() string { @@ -4096,7 +4434,7 @@ type DealInfo struct { func (x *DealInfo) Reset() { *x = DealInfo{} if protoimpl.UnsafeEnabled { - mi := &file_powergate_user_v1_user_proto_msgTypes[73] + mi := &file_powergate_user_v1_user_proto_msgTypes[79] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4109,7 +4447,7 @@ func (x *DealInfo) String() string { func (*DealInfo) ProtoMessage() {} func (x *DealInfo) ProtoReflect() protoreflect.Message { - mi := &file_powergate_user_v1_user_proto_msgTypes[73] + mi := &file_powergate_user_v1_user_proto_msgTypes[79] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4122,7 +4460,7 @@ func (x *DealInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use DealInfo.ProtoReflect.Descriptor instead. func (*DealInfo) Descriptor() ([]byte, []int) { - return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{73} + return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{79} } func (x *DealInfo) GetProposalCid() string { @@ -4227,7 +4565,7 @@ type StorageJob struct { func (x *StorageJob) Reset() { *x = StorageJob{} if protoimpl.UnsafeEnabled { - mi := &file_powergate_user_v1_user_proto_msgTypes[74] + mi := &file_powergate_user_v1_user_proto_msgTypes[80] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4240,7 +4578,7 @@ func (x *StorageJob) String() string { func (*StorageJob) ProtoMessage() {} func (x *StorageJob) ProtoReflect() protoreflect.Message { - mi := &file_powergate_user_v1_user_proto_msgTypes[74] + mi := &file_powergate_user_v1_user_proto_msgTypes[80] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4253,7 +4591,7 @@ func (x *StorageJob) ProtoReflect() protoreflect.Message { // Deprecated: Use StorageJob.ProtoReflect.Descriptor instead. func (*StorageJob) Descriptor() ([]byte, []int) { - return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{74} + return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{80} } func (x *StorageJob) GetId() string { @@ -4325,7 +4663,7 @@ type DealError struct { func (x *DealError) Reset() { *x = DealError{} if protoimpl.UnsafeEnabled { - mi := &file_powergate_user_v1_user_proto_msgTypes[75] + mi := &file_powergate_user_v1_user_proto_msgTypes[81] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4338,7 +4676,7 @@ func (x *DealError) String() string { func (*DealError) ProtoMessage() {} func (x *DealError) ProtoReflect() protoreflect.Message { - mi := &file_powergate_user_v1_user_proto_msgTypes[75] + mi := &file_powergate_user_v1_user_proto_msgTypes[81] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4351,7 +4689,7 @@ func (x *DealError) ProtoReflect() protoreflect.Message { // Deprecated: Use DealError.ProtoReflect.Descriptor instead. func (*DealError) Descriptor() ([]byte, []int) { - return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{75} + return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{81} } func (x *DealError) GetProposalCid() string { @@ -4389,7 +4727,7 @@ type LogEntry struct { func (x *LogEntry) Reset() { *x = LogEntry{} if protoimpl.UnsafeEnabled { - mi := &file_powergate_user_v1_user_proto_msgTypes[76] + mi := &file_powergate_user_v1_user_proto_msgTypes[82] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4402,7 +4740,7 @@ func (x *LogEntry) String() string { func (*LogEntry) ProtoMessage() {} func (x *LogEntry) ProtoReflect() protoreflect.Message { - mi := &file_powergate_user_v1_user_proto_msgTypes[76] + mi := &file_powergate_user_v1_user_proto_msgTypes[82] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4415,7 +4753,7 @@ func (x *LogEntry) ProtoReflect() protoreflect.Message { // Deprecated: Use LogEntry.ProtoReflect.Descriptor instead. func (*LogEntry) Descriptor() ([]byte, []int) { - return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{76} + return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{82} } func (x *LogEntry) GetCid() string { @@ -4462,7 +4800,7 @@ type DealRecordsConfig struct { func (x *DealRecordsConfig) Reset() { *x = DealRecordsConfig{} if protoimpl.UnsafeEnabled { - mi := &file_powergate_user_v1_user_proto_msgTypes[77] + mi := &file_powergate_user_v1_user_proto_msgTypes[83] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4475,7 +4813,7 @@ func (x *DealRecordsConfig) String() string { func (*DealRecordsConfig) ProtoMessage() {} func (x *DealRecordsConfig) ProtoReflect() protoreflect.Message { - mi := &file_powergate_user_v1_user_proto_msgTypes[77] + mi := &file_powergate_user_v1_user_proto_msgTypes[83] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4488,7 +4826,7 @@ func (x *DealRecordsConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use DealRecordsConfig.ProtoReflect.Descriptor instead. func (*DealRecordsConfig) Descriptor() ([]byte, []int) { - return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{77} + return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{83} } func (x *DealRecordsConfig) GetFromAddrs() []string { @@ -4555,7 +4893,7 @@ type StorageDealInfo struct { func (x *StorageDealInfo) Reset() { *x = StorageDealInfo{} if protoimpl.UnsafeEnabled { - mi := &file_powergate_user_v1_user_proto_msgTypes[78] + mi := &file_powergate_user_v1_user_proto_msgTypes[84] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4568,7 +4906,7 @@ func (x *StorageDealInfo) String() string { func (*StorageDealInfo) ProtoMessage() {} func (x *StorageDealInfo) ProtoReflect() protoreflect.Message { - mi := &file_powergate_user_v1_user_proto_msgTypes[78] + mi := &file_powergate_user_v1_user_proto_msgTypes[84] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4581,7 +4919,7 @@ func (x *StorageDealInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use StorageDealInfo.ProtoReflect.Descriptor instead. func (*StorageDealInfo) Descriptor() ([]byte, []int) { - return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{78} + return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{84} } func (x *StorageDealInfo) GetProposalCid() string { @@ -4690,7 +5028,7 @@ type StorageDealRecord struct { func (x *StorageDealRecord) Reset() { *x = StorageDealRecord{} if protoimpl.UnsafeEnabled { - mi := &file_powergate_user_v1_user_proto_msgTypes[79] + mi := &file_powergate_user_v1_user_proto_msgTypes[85] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4703,7 +5041,7 @@ func (x *StorageDealRecord) String() string { func (*StorageDealRecord) ProtoMessage() {} func (x *StorageDealRecord) ProtoReflect() protoreflect.Message { - mi := &file_powergate_user_v1_user_proto_msgTypes[79] + mi := &file_powergate_user_v1_user_proto_msgTypes[85] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4716,7 +5054,7 @@ func (x *StorageDealRecord) ProtoReflect() protoreflect.Message { // Deprecated: Use StorageDealRecord.ProtoReflect.Descriptor instead. func (*StorageDealRecord) Descriptor() ([]byte, []int) { - return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{79} + return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{85} } func (x *StorageDealRecord) GetRootCid() string { @@ -4820,7 +5158,7 @@ type RetrievalDealInfo struct { func (x *RetrievalDealInfo) Reset() { *x = RetrievalDealInfo{} if protoimpl.UnsafeEnabled { - mi := &file_powergate_user_v1_user_proto_msgTypes[80] + mi := &file_powergate_user_v1_user_proto_msgTypes[86] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4833,7 +5171,7 @@ func (x *RetrievalDealInfo) String() string { func (*RetrievalDealInfo) ProtoMessage() {} func (x *RetrievalDealInfo) ProtoReflect() protoreflect.Message { - mi := &file_powergate_user_v1_user_proto_msgTypes[80] + mi := &file_powergate_user_v1_user_proto_msgTypes[86] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4846,7 +5184,7 @@ func (x *RetrievalDealInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use RetrievalDealInfo.ProtoReflect.Descriptor instead. func (*RetrievalDealInfo) Descriptor() ([]byte, []int) { - return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{80} + return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{86} } func (x *RetrievalDealInfo) GetRootCid() string { @@ -4917,7 +5255,7 @@ type RetrievalDealRecord struct { func (x *RetrievalDealRecord) Reset() { *x = RetrievalDealRecord{} if protoimpl.UnsafeEnabled { - mi := &file_powergate_user_v1_user_proto_msgTypes[81] + mi := &file_powergate_user_v1_user_proto_msgTypes[87] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4930,7 +5268,7 @@ func (x *RetrievalDealRecord) String() string { func (*RetrievalDealRecord) ProtoMessage() {} func (x *RetrievalDealRecord) ProtoReflect() protoreflect.Message { - mi := &file_powergate_user_v1_user_proto_msgTypes[81] + mi := &file_powergate_user_v1_user_proto_msgTypes[87] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4943,7 +5281,7 @@ func (x *RetrievalDealRecord) ProtoReflect() protoreflect.Message { // Deprecated: Use RetrievalDealRecord.ProtoReflect.Descriptor instead. func (*RetrievalDealRecord) Descriptor() ([]byte, []int) { - return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{81} + return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{87} } func (x *RetrievalDealRecord) GetAddress() string { @@ -5020,7 +5358,7 @@ type AddrInfo_VerifiedClientInfo struct { func (x *AddrInfo_VerifiedClientInfo) Reset() { *x = AddrInfo_VerifiedClientInfo{} if protoimpl.UnsafeEnabled { - mi := &file_powergate_user_v1_user_proto_msgTypes[82] + mi := &file_powergate_user_v1_user_proto_msgTypes[88] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5033,7 +5371,7 @@ func (x *AddrInfo_VerifiedClientInfo) String() string { func (*AddrInfo_VerifiedClientInfo) ProtoMessage() {} func (x *AddrInfo_VerifiedClientInfo) ProtoReflect() protoreflect.Message { - mi := &file_powergate_user_v1_user_proto_msgTypes[82] + mi := &file_powergate_user_v1_user_proto_msgTypes[88] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5402,7 +5740,46 @@ var file_powergate_user_v1_user_proto_rawDesc = []byte{ 0x65, 0x63, 0x6f, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x69, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x63, - 0x6f, 0x69, 0x6e, 0x22, 0x92, 0x01, 0x0a, 0x0d, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x43, + 0x6f, 0x69, 0x6e, 0x22, 0x49, 0x0a, 0x0f, 0x57, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x41, 0x75, + 0x74, 0x68, 0x44, 0x61, 0x74, 0x61, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, + 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0x63, + 0x0a, 0x15, 0x57, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, + 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x36, 0x0a, 0x04, 0x64, + 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x70, 0x6f, 0x77, 0x65, + 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x57, 0x65, + 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x41, 0x75, 0x74, 0x68, 0x44, 0x61, 0x74, 0x61, 0x52, 0x04, 0x64, + 0x61, 0x74, 0x61, 0x22, 0x77, 0x0a, 0x07, 0x57, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x12, 0x1a, + 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x08, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x50, 0x0a, 0x0e, 0x61, 0x75, + 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, + 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x57, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x41, 0x75, + 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0e, 0x61, 0x75, + 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x40, 0x0a, 0x0c, + 0x57, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x41, 0x6c, 0x65, 0x72, 0x74, 0x12, 0x12, 0x0a, 0x04, + 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, + 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x09, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x22, 0x67, + 0x0a, 0x14, 0x57, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x37, + 0x0a, 0x06, 0x61, 0x6c, 0x65, 0x72, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, + 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, + 0x76, 0x31, 0x2e, 0x57, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x41, 0x6c, 0x65, 0x72, 0x74, 0x52, + 0x06, 0x61, 0x6c, 0x65, 0x72, 0x74, 0x73, 0x22, 0x99, 0x01, 0x0a, 0x12, 0x4e, 0x6f, 0x74, 0x69, + 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x34, + 0x0a, 0x07, 0x77, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1a, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, + 0x2e, 0x76, 0x31, 0x2e, 0x57, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x52, 0x07, 0x77, 0x65, 0x62, + 0x68, 0x6f, 0x6f, 0x6b, 0x12, 0x4d, 0x0a, 0x0d, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x70, 0x6f, + 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, + 0x57, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0d, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x22, 0xdf, 0x01, 0x0a, 0x0d, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2e, 0x0a, 0x03, 0x68, 0x6f, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, @@ -5411,459 +5788,464 @@ var file_powergate_user_v1_user_proto_rawDesc = []byte{ 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6c, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x04, 0x63, 0x6f, 0x6c, 0x64, 0x12, 0x1e, 0x0a, 0x0a, 0x72, 0x65, 0x70, 0x61, 0x69, 0x72, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x72, 0x65, - 0x70, 0x61, 0x69, 0x72, 0x61, 0x62, 0x6c, 0x65, 0x22, 0x27, 0x0a, 0x0b, 0x49, 0x70, 0x66, 0x73, - 0x48, 0x6f, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, - 0x64, 0x22, 0x6b, 0x0a, 0x07, 0x48, 0x6f, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x18, 0x0a, 0x07, - 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, - 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x32, 0x0a, 0x04, 0x69, 0x70, - 0x66, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, - 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x70, 0x66, - 0x73, 0x48, 0x6f, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x04, 0x69, 0x70, 0x66, 0x73, 0x22, 0xd0, - 0x01, 0x0a, 0x0a, 0x46, 0x69, 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x12, 0x17, 0x0a, - 0x07, 0x64, 0x65, 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, - 0x64, 0x65, 0x61, 0x6c, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x72, 0x65, 0x6e, 0x65, 0x77, 0x65, - 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x72, 0x65, 0x6e, 0x65, 0x77, 0x65, 0x64, - 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, - 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x04, 0x52, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x45, 0x70, 0x6f, 0x63, 0x68, 0x12, 0x14, 0x0a, - 0x05, 0x6d, 0x69, 0x6e, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6d, 0x69, - 0x6e, 0x65, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x5f, 0x70, 0x72, 0x69, - 0x63, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x50, - 0x72, 0x69, 0x63, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x69, 0x65, 0x63, 0x65, 0x5f, 0x63, 0x69, - 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x69, 0x65, 0x63, 0x65, 0x43, 0x69, - 0x64, 0x22, 0x75, 0x0a, 0x07, 0x46, 0x69, 0x6c, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x19, 0x0a, 0x08, - 0x64, 0x61, 0x74, 0x61, 0x5f, 0x63, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, - 0x64, 0x61, 0x74, 0x61, 0x43, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x3b, 0x0a, 0x09, 0x70, - 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, - 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, - 0x76, 0x31, 0x2e, 0x46, 0x69, 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x52, 0x09, 0x70, - 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x73, 0x22, 0x5c, 0x0a, 0x08, 0x43, 0x6f, 0x6c, 0x64, - 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x36, - 0x0a, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x63, 0x6f, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x1a, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, - 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x69, 0x6c, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x08, 0x66, 0x69, - 0x6c, 0x65, 0x63, 0x6f, 0x69, 0x6e, 0x22, 0xaf, 0x01, 0x0a, 0x0b, 0x53, 0x74, 0x6f, 0x72, 0x61, - 0x67, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x15, 0x0a, 0x06, 0x6a, 0x6f, 0x62, 0x5f, 0x69, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x64, 0x12, 0x10, 0x0a, - 0x03, 0x63, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x63, 0x69, 0x64, 0x12, - 0x18, 0x0a, 0x07, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x07, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x12, 0x2c, 0x0a, 0x03, 0x68, 0x6f, 0x74, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, - 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x74, 0x49, 0x6e, - 0x66, 0x6f, 0x52, 0x03, 0x68, 0x6f, 0x74, 0x12, 0x2f, 0x0a, 0x04, 0x63, 0x6f, 0x6c, 0x64, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, - 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6c, 0x64, 0x49, 0x6e, - 0x66, 0x6f, 0x52, 0x04, 0x63, 0x6f, 0x6c, 0x64, 0x22, 0xf2, 0x02, 0x0a, 0x07, 0x43, 0x69, 0x64, - 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x10, 0x0a, 0x03, 0x63, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x03, 0x63, 0x69, 0x64, 0x12, 0x61, 0x0a, 0x1c, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, - 0x5f, 0x70, 0x75, 0x73, 0x68, 0x65, 0x64, 0x5f, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x5f, - 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x70, - 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, - 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x19, - 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x50, 0x75, 0x73, 0x68, 0x65, 0x64, 0x53, 0x74, 0x6f, 0x72, - 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x50, 0x0a, 0x14, 0x63, 0x75, 0x72, - 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x5f, 0x69, 0x6e, 0x66, - 0x6f, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, - 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x6f, 0x72, - 0x61, 0x67, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x12, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, - 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x4d, 0x0a, 0x13, 0x71, - 0x75, 0x65, 0x75, 0x65, 0x64, 0x5f, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x5f, 0x6a, 0x6f, - 0x62, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, - 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x6f, - 0x72, 0x61, 0x67, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x11, 0x71, 0x75, 0x65, 0x75, 0x65, 0x64, 0x53, - 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x4a, 0x6f, 0x62, 0x73, 0x12, 0x51, 0x0a, 0x15, 0x65, 0x78, - 0x65, 0x63, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x5f, - 0x6a, 0x6f, 0x62, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x6f, 0x77, 0x65, + 0x70, 0x61, 0x69, 0x72, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x4b, 0x0a, 0x0d, 0x6e, 0x6f, 0x74, 0x69, + 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x25, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, + 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0d, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x27, 0x0a, 0x0b, 0x49, 0x70, 0x66, 0x73, 0x48, 0x6f, 0x74, + 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x22, 0x6b, + 0x0a, 0x07, 0x48, 0x6f, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, + 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, + 0x6c, 0x65, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x32, 0x0a, 0x04, 0x69, 0x70, 0x66, 0x73, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, + 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x70, 0x66, 0x73, 0x48, 0x6f, + 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x04, 0x69, 0x70, 0x66, 0x73, 0x22, 0xd0, 0x01, 0x0a, 0x0a, + 0x46, 0x69, 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x64, 0x65, + 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x64, 0x65, 0x61, + 0x6c, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x72, 0x65, 0x6e, 0x65, 0x77, 0x65, 0x64, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x72, 0x65, 0x6e, 0x65, 0x77, 0x65, 0x64, 0x12, 0x1a, 0x0a, + 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x74, 0x61, + 0x72, 0x74, 0x5f, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, + 0x73, 0x74, 0x61, 0x72, 0x74, 0x45, 0x70, 0x6f, 0x63, 0x68, 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x69, + 0x6e, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6d, 0x69, 0x6e, 0x65, 0x72, + 0x12, 0x1f, 0x0a, 0x0b, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x50, 0x72, 0x69, 0x63, + 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x69, 0x65, 0x63, 0x65, 0x5f, 0x63, 0x69, 0x64, 0x18, 0x07, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x69, 0x65, 0x63, 0x65, 0x43, 0x69, 0x64, 0x22, 0x75, + 0x0a, 0x07, 0x46, 0x69, 0x6c, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x19, 0x0a, 0x08, 0x64, 0x61, 0x74, + 0x61, 0x5f, 0x63, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x64, 0x61, 0x74, + 0x61, 0x43, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x04, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x3b, 0x0a, 0x09, 0x70, 0x72, 0x6f, 0x70, + 0x6f, 0x73, 0x61, 0x6c, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x6f, + 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, + 0x46, 0x69, 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x52, 0x09, 0x70, 0x72, 0x6f, 0x70, + 0x6f, 0x73, 0x61, 0x6c, 0x73, 0x22, 0x5c, 0x0a, 0x08, 0x43, 0x6f, 0x6c, 0x64, 0x49, 0x6e, 0x66, + 0x6f, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x36, 0x0a, 0x08, 0x66, + 0x69, 0x6c, 0x65, 0x63, 0x6f, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, + 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, + 0x31, 0x2e, 0x46, 0x69, 0x6c, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x63, + 0x6f, 0x69, 0x6e, 0x22, 0xaf, 0x01, 0x0a, 0x0b, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x49, + 0x6e, 0x66, 0x6f, 0x12, 0x15, 0x0a, 0x06, 0x6a, 0x6f, 0x62, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x63, 0x69, + 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x63, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, + 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x63, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x12, 0x2c, 0x0a, 0x03, 0x68, 0x6f, 0x74, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, + 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, + 0x03, 0x68, 0x6f, 0x74, 0x12, 0x2f, 0x0a, 0x04, 0x63, 0x6f, 0x6c, 0x64, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, + 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6c, 0x64, 0x49, 0x6e, 0x66, 0x6f, 0x52, + 0x04, 0x63, 0x6f, 0x6c, 0x64, 0x22, 0xf2, 0x02, 0x0a, 0x07, 0x43, 0x69, 0x64, 0x49, 0x6e, 0x66, + 0x6f, 0x12, 0x10, 0x0a, 0x03, 0x63, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, + 0x63, 0x69, 0x64, 0x12, 0x61, 0x0a, 0x1c, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x70, 0x75, + 0x73, 0x68, 0x65, 0x64, 0x5f, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, - 0x6f, 0x72, 0x61, 0x67, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x13, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, - 0x69, 0x6e, 0x67, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x4a, 0x6f, 0x62, 0x22, 0xf1, 0x02, - 0x0a, 0x08, 0x44, 0x65, 0x61, 0x6c, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x72, - 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x5f, 0x63, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0b, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x43, 0x69, 0x64, 0x12, 0x19, 0x0a, - 0x08, 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, - 0x07, 0x73, 0x74, 0x61, 0x74, 0x65, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x74, - 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x74, - 0x61, 0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x69, 0x6e, 0x65, 0x72, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6d, 0x69, 0x6e, 0x65, 0x72, 0x12, 0x1b, 0x0a, - 0x09, 0x70, 0x69, 0x65, 0x63, 0x65, 0x5f, 0x63, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x08, 0x70, 0x69, 0x65, 0x63, 0x65, 0x43, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, - 0x7a, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x26, - 0x0a, 0x0f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x65, 0x70, 0x6f, 0x63, - 0x68, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, 0x70, 0x72, 0x69, 0x63, 0x65, 0x50, 0x65, - 0x72, 0x45, 0x70, 0x6f, 0x63, 0x68, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, - 0x65, 0x70, 0x6f, 0x63, 0x68, 0x18, 0x08, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x73, 0x74, 0x61, - 0x72, 0x74, 0x45, 0x70, 0x6f, 0x63, 0x68, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x12, 0x17, 0x0a, 0x07, 0x64, 0x65, 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x0a, - 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x64, 0x65, 0x61, 0x6c, 0x49, 0x64, 0x12, 0x29, 0x0a, 0x10, - 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x65, 0x70, 0x6f, 0x63, 0x68, - 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x45, 0x70, 0x6f, 0x63, 0x68, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, - 0x67, 0x65, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, - 0x65, 0x22, 0xb4, 0x02, 0x0a, 0x0a, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x4a, 0x6f, 0x62, - 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, - 0x12, 0x15, 0x0a, 0x06, 0x61, 0x70, 0x69, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x05, 0x61, 0x70, 0x69, 0x49, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x63, 0x69, 0x64, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x63, 0x69, 0x64, 0x12, 0x34, 0x0a, 0x06, 0x73, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x70, 0x6f, 0x77, 0x65, - 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4a, 0x6f, - 0x62, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, - 0x1f, 0x0a, 0x0b, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x63, 0x61, 0x75, 0x73, 0x65, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x43, 0x61, 0x75, 0x73, 0x65, - 0x12, 0x38, 0x0a, 0x09, 0x64, 0x65, 0x61, 0x6c, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x06, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, - 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x61, 0x6c, 0x49, 0x6e, 0x66, 0x6f, - 0x52, 0x08, 0x64, 0x65, 0x61, 0x6c, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x3d, 0x0a, 0x0b, 0x64, 0x65, - 0x61, 0x6c, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x1c, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, - 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x61, 0x6c, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x0a, 0x64, - 0x65, 0x61, 0x6c, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x72, 0x65, - 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x22, 0x5e, 0x0a, 0x09, 0x44, 0x65, 0x61, 0x6c, - 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, - 0x6c, 0x5f, 0x63, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x72, 0x6f, - 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x43, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x69, 0x6e, 0x65, - 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6d, 0x69, 0x6e, 0x65, 0x72, 0x12, 0x18, - 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x61, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x45, - 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x63, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x03, 0x63, 0x69, 0x64, 0x12, 0x15, 0x0a, 0x06, 0x6a, 0x6f, 0x62, 0x5f, 0x69, 0x64, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x64, 0x12, 0x12, 0x0a, - 0x04, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x74, 0x69, 0x6d, - 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0xe2, 0x01, 0x0a, 0x11, - 0x44, 0x65, 0x61, 0x6c, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x12, 0x1d, 0x0a, 0x0a, 0x66, 0x72, 0x6f, 0x6d, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x73, 0x18, - 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x66, 0x72, 0x6f, 0x6d, 0x41, 0x64, 0x64, 0x72, 0x73, - 0x12, 0x1b, 0x0a, 0x09, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x63, 0x69, 0x64, 0x73, 0x18, 0x02, 0x20, - 0x03, 0x28, 0x09, 0x52, 0x08, 0x64, 0x61, 0x74, 0x61, 0x43, 0x69, 0x64, 0x73, 0x12, 0x27, 0x0a, - 0x0f, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x50, - 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x23, 0x0a, 0x0d, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, - 0x65, 0x5f, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x69, - 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x12, 0x1c, 0x0a, 0x09, 0x61, - 0x73, 0x63, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, - 0x61, 0x73, 0x63, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x25, 0x0a, 0x0e, 0x69, 0x6e, 0x63, - 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x0d, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, - 0x22, 0xf8, 0x02, 0x0a, 0x0f, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x44, 0x65, 0x61, 0x6c, - 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, - 0x5f, 0x63, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x72, 0x6f, 0x70, - 0x6f, 0x73, 0x61, 0x6c, 0x43, 0x69, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x74, 0x61, 0x74, 0x65, - 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x73, 0x74, 0x61, 0x74, 0x65, - 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x74, 0x61, 0x74, 0x65, 0x4e, 0x61, 0x6d, - 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x69, 0x6e, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x05, 0x6d, 0x69, 0x6e, 0x65, 0x72, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x69, 0x65, 0x63, 0x65, - 0x5f, 0x63, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x69, 0x65, 0x63, - 0x65, 0x43, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x06, 0x20, 0x01, - 0x28, 0x04, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x70, 0x72, 0x69, 0x63, - 0x65, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x18, 0x07, 0x20, 0x01, 0x28, - 0x04, 0x52, 0x0d, 0x70, 0x72, 0x69, 0x63, 0x65, 0x50, 0x65, 0x72, 0x45, 0x70, 0x6f, 0x63, 0x68, - 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x18, - 0x08, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x45, 0x70, 0x6f, 0x63, - 0x68, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x09, 0x20, - 0x01, 0x28, 0x04, 0x52, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x17, 0x0a, - 0x07, 0x64, 0x65, 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, - 0x64, 0x65, 0x61, 0x6c, 0x49, 0x64, 0x12, 0x29, 0x0a, 0x10, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0f, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x70, 0x6f, 0x63, - 0x68, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x0c, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0xc2, 0x04, 0x0a, 0x11, - 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x44, 0x65, 0x61, 0x6c, 0x52, 0x65, 0x63, 0x6f, 0x72, - 0x64, 0x12, 0x19, 0x0a, 0x08, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x63, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x07, 0x72, 0x6f, 0x6f, 0x74, 0x43, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, - 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, - 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x65, - 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x70, 0x65, 0x6e, - 0x64, 0x69, 0x6e, 0x67, 0x12, 0x3f, 0x0a, 0x09, 0x64, 0x65, 0x61, 0x6c, 0x5f, 0x69, 0x6e, 0x66, - 0x6f, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, - 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x6f, 0x72, - 0x61, 0x67, 0x65, 0x44, 0x65, 0x61, 0x6c, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x08, 0x64, 0x65, 0x61, - 0x6c, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x23, 0x0a, 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, - 0x72, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x72, - 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x4a, 0x0a, 0x13, 0x64, 0x61, - 0x74, 0x61, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x5f, 0x73, 0x74, 0x61, 0x72, - 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, - 0x61, 0x6d, 0x70, 0x52, 0x11, 0x64, 0x61, 0x74, 0x61, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, - 0x72, 0x53, 0x74, 0x61, 0x72, 0x74, 0x12, 0x46, 0x0a, 0x11, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x74, - 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x5f, 0x65, 0x6e, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0f, 0x64, - 0x61, 0x74, 0x61, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x45, 0x6e, 0x64, 0x12, 0x3f, - 0x0a, 0x0d, 0x73, 0x65, 0x61, 0x6c, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, - 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, - 0x70, 0x52, 0x0c, 0x73, 0x65, 0x61, 0x6c, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x72, 0x74, 0x12, - 0x3b, 0x0a, 0x0b, 0x73, 0x65, 0x61, 0x6c, 0x69, 0x6e, 0x67, 0x5f, 0x65, 0x6e, 0x64, 0x18, 0x0a, + 0x6f, 0x72, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x19, 0x6c, 0x61, 0x74, + 0x65, 0x73, 0x74, 0x50, 0x75, 0x73, 0x68, 0x65, 0x64, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x50, 0x0a, 0x14, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, + 0x74, 0x5f, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, + 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, + 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x12, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x6f, + 0x72, 0x61, 0x67, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x4d, 0x0a, 0x13, 0x71, 0x75, 0x65, 0x75, + 0x65, 0x64, 0x5f, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x5f, 0x6a, 0x6f, 0x62, 0x73, 0x18, + 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, + 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, + 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x11, 0x71, 0x75, 0x65, 0x75, 0x65, 0x64, 0x53, 0x74, 0x6f, 0x72, + 0x61, 0x67, 0x65, 0x4a, 0x6f, 0x62, 0x73, 0x12, 0x51, 0x0a, 0x15, 0x65, 0x78, 0x65, 0x63, 0x75, + 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x5f, 0x6a, 0x6f, 0x62, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, + 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, + 0x67, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x13, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6e, 0x67, + 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x4a, 0x6f, 0x62, 0x22, 0xf1, 0x02, 0x0a, 0x08, 0x44, + 0x65, 0x61, 0x6c, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x70, 0x6f, + 0x73, 0x61, 0x6c, 0x5f, 0x63, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, + 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x43, 0x69, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x74, + 0x61, 0x74, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x73, 0x74, + 0x61, 0x74, 0x65, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f, 0x6e, + 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x74, 0x61, 0x74, 0x65, + 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x69, 0x6e, 0x65, 0x72, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x05, 0x6d, 0x69, 0x6e, 0x65, 0x72, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x69, + 0x65, 0x63, 0x65, 0x5f, 0x63, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, + 0x69, 0x65, 0x63, 0x65, 0x43, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x70, + 0x72, 0x69, 0x63, 0x65, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x18, 0x07, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, 0x70, 0x72, 0x69, 0x63, 0x65, 0x50, 0x65, 0x72, 0x45, 0x70, + 0x6f, 0x63, 0x68, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x65, 0x70, 0x6f, + 0x63, 0x68, 0x18, 0x08, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x45, + 0x70, 0x6f, 0x63, 0x68, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x18, 0x09, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x12, 0x17, 0x0a, 0x07, 0x64, 0x65, 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, + 0x04, 0x52, 0x06, 0x64, 0x65, 0x61, 0x6c, 0x49, 0x64, 0x12, 0x29, 0x0a, 0x10, 0x61, 0x63, 0x74, + 0x69, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x18, 0x0b, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0f, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, + 0x70, 0x6f, 0x63, 0x68, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, + 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0xb4, + 0x02, 0x0a, 0x0a, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x4a, 0x6f, 0x62, 0x12, 0x0e, 0x0a, + 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x15, 0x0a, + 0x06, 0x61, 0x70, 0x69, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, + 0x70, 0x69, 0x49, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x63, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x03, 0x63, 0x69, 0x64, 0x12, 0x34, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, + 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4a, 0x6f, 0x62, 0x53, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1f, 0x0a, 0x0b, + 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x63, 0x61, 0x75, 0x73, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0a, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x43, 0x61, 0x75, 0x73, 0x65, 0x12, 0x38, 0x0a, + 0x09, 0x64, 0x65, 0x61, 0x6c, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x1b, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, + 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x61, 0x6c, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x08, 0x64, + 0x65, 0x61, 0x6c, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x3d, 0x0a, 0x0b, 0x64, 0x65, 0x61, 0x6c, 0x5f, + 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x70, + 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, + 0x2e, 0x44, 0x65, 0x61, 0x6c, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x0a, 0x64, 0x65, 0x61, 0x6c, + 0x45, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x64, 0x5f, 0x61, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x64, 0x41, 0x74, 0x22, 0x5e, 0x0a, 0x09, 0x44, 0x65, 0x61, 0x6c, 0x45, 0x72, 0x72, + 0x6f, 0x72, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x5f, 0x63, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, + 0x61, 0x6c, 0x43, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x69, 0x6e, 0x65, 0x72, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6d, 0x69, 0x6e, 0x65, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x6d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x61, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x45, 0x6e, 0x74, 0x72, + 0x79, 0x12, 0x10, 0x0a, 0x03, 0x63, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, + 0x63, 0x69, 0x64, 0x12, 0x15, 0x0a, 0x06, 0x6a, 0x6f, 0x62, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x69, + 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x18, + 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0xe2, 0x01, 0x0a, 0x11, 0x44, 0x65, 0x61, + 0x6c, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1d, + 0x0a, 0x0a, 0x66, 0x72, 0x6f, 0x6d, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x09, 0x52, 0x09, 0x66, 0x72, 0x6f, 0x6d, 0x41, 0x64, 0x64, 0x72, 0x73, 0x12, 0x1b, 0x0a, + 0x09, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x63, 0x69, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, + 0x52, 0x08, 0x64, 0x61, 0x74, 0x61, 0x43, 0x69, 0x64, 0x73, 0x12, 0x27, 0x0a, 0x0f, 0x69, 0x6e, + 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x0e, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x50, 0x65, 0x6e, 0x64, + 0x69, 0x6e, 0x67, 0x12, 0x23, 0x0a, 0x0d, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x66, + 0x69, 0x6e, 0x61, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x69, 0x6e, 0x63, 0x6c, + 0x75, 0x64, 0x65, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x73, 0x63, 0x65, + 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x61, 0x73, 0x63, + 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x25, 0x0a, 0x0e, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, + 0x65, 0x5f, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, + 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x22, 0xf8, 0x02, + 0x0a, 0x0f, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x44, 0x65, 0x61, 0x6c, 0x49, 0x6e, 0x66, + 0x6f, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x5f, 0x63, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, + 0x6c, 0x43, 0x69, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f, 0x69, 0x64, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x73, 0x74, 0x61, 0x74, 0x65, 0x49, 0x64, 0x12, + 0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x74, 0x61, 0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x14, + 0x0a, 0x05, 0x6d, 0x69, 0x6e, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6d, + 0x69, 0x6e, 0x65, 0x72, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x69, 0x65, 0x63, 0x65, 0x5f, 0x63, 0x69, + 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x69, 0x65, 0x63, 0x65, 0x43, 0x69, + 0x64, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, + 0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x5f, 0x70, + 0x65, 0x72, 0x5f, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, + 0x70, 0x72, 0x69, 0x63, 0x65, 0x50, 0x65, 0x72, 0x45, 0x70, 0x6f, 0x63, 0x68, 0x12, 0x1f, 0x0a, + 0x0b, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x18, 0x08, 0x20, 0x01, + 0x28, 0x04, 0x52, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x45, 0x70, 0x6f, 0x63, 0x68, 0x12, 0x1a, + 0x0a, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x04, + 0x52, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x17, 0x0a, 0x07, 0x64, 0x65, + 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x64, 0x65, 0x61, + 0x6c, 0x49, 0x64, 0x12, 0x29, 0x0a, 0x10, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x5f, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x61, + 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x70, 0x6f, 0x63, 0x68, 0x12, 0x18, + 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0xc2, 0x04, 0x0a, 0x11, 0x53, 0x74, 0x6f, + 0x72, 0x61, 0x67, 0x65, 0x44, 0x65, 0x61, 0x6c, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x12, 0x19, + 0x0a, 0x08, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x63, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x07, 0x72, 0x6f, 0x6f, 0x74, 0x43, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, + 0x65, 0x73, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x65, 0x6e, 0x64, 0x69, + 0x6e, 0x67, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, + 0x67, 0x12, 0x3f, 0x0a, 0x09, 0x64, 0x65, 0x61, 0x6c, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, + 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, + 0x44, 0x65, 0x61, 0x6c, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x08, 0x64, 0x65, 0x61, 0x6c, 0x49, 0x6e, + 0x66, 0x6f, 0x12, 0x23, 0x0a, 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x5f, 0x73, + 0x69, 0x7a, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x72, 0x61, 0x6e, 0x73, + 0x66, 0x65, 0x72, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x4a, 0x0a, 0x13, 0x64, 0x61, 0x74, 0x61, 0x5f, + 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, - 0x52, 0x0a, 0x73, 0x65, 0x61, 0x6c, 0x69, 0x6e, 0x67, 0x45, 0x6e, 0x64, 0x12, 0x17, 0x0a, 0x07, - 0x65, 0x72, 0x72, 0x5f, 0x6d, 0x73, 0x67, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x65, - 0x72, 0x72, 0x4d, 0x73, 0x67, 0x12, 0x39, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, - 0x5f, 0x61, 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, - 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, - 0x22, 0x80, 0x02, 0x0a, 0x11, 0x52, 0x65, 0x74, 0x72, 0x69, 0x65, 0x76, 0x61, 0x6c, 0x44, 0x65, - 0x61, 0x6c, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x19, 0x0a, 0x08, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x63, - 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x72, 0x6f, 0x6f, 0x74, 0x43, 0x69, - 0x64, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, - 0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x69, 0x6e, 0x5f, 0x70, 0x72, 0x69, - 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x6d, 0x69, 0x6e, 0x50, 0x72, 0x69, - 0x63, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x6e, - 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x70, 0x61, - 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x3a, 0x0a, - 0x19, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, - 0x6c, 0x5f, 0x69, 0x6e, 0x63, 0x72, 0x65, 0x61, 0x73, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, - 0x52, 0x17, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, - 0x6c, 0x49, 0x6e, 0x63, 0x72, 0x65, 0x61, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x69, 0x6e, - 0x65, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6d, 0x69, 0x6e, 0x65, 0x72, 0x12, - 0x22, 0x0a, 0x0d, 0x6d, 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x69, 0x64, - 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6d, 0x69, 0x6e, 0x65, 0x72, 0x50, 0x65, 0x65, - 0x72, 0x49, 0x64, 0x22, 0xa5, 0x03, 0x0a, 0x13, 0x52, 0x65, 0x74, 0x72, 0x69, 0x65, 0x76, 0x61, - 0x6c, 0x44, 0x65, 0x61, 0x6c, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 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, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x41, 0x0a, 0x09, 0x64, 0x65, 0x61, - 0x6c, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x70, - 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, - 0x2e, 0x52, 0x65, 0x74, 0x72, 0x69, 0x65, 0x76, 0x61, 0x6c, 0x44, 0x65, 0x61, 0x6c, 0x49, 0x6e, - 0x66, 0x6f, 0x52, 0x08, 0x64, 0x65, 0x61, 0x6c, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x4a, 0x0a, 0x13, - 0x64, 0x61, 0x74, 0x61, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x5f, 0x73, 0x74, - 0x61, 0x72, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, - 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x11, 0x64, 0x61, 0x74, 0x61, 0x54, 0x72, 0x61, 0x6e, 0x73, - 0x66, 0x65, 0x72, 0x53, 0x74, 0x61, 0x72, 0x74, 0x12, 0x46, 0x0a, 0x11, 0x64, 0x61, 0x74, 0x61, - 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x5f, 0x65, 0x6e, 0x64, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, - 0x0f, 0x64, 0x61, 0x74, 0x61, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x45, 0x6e, 0x64, - 0x12, 0x17, 0x0a, 0x07, 0x65, 0x72, 0x72, 0x5f, 0x6d, 0x73, 0x67, 0x18, 0x06, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x06, 0x65, 0x72, 0x72, 0x4d, 0x73, 0x67, 0x12, 0x39, 0x0a, 0x0a, 0x75, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, - 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, - 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x75, 0x70, 0x64, 0x61, 0x74, - 0x65, 0x64, 0x41, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x02, 0x69, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x72, 0x65, - 0x63, 0x65, 0x69, 0x76, 0x65, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, 0x62, 0x79, - 0x74, 0x65, 0x73, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x64, 0x2a, 0xa0, 0x01, 0x0a, 0x09, - 0x4a, 0x6f, 0x62, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1a, 0x0a, 0x16, 0x4a, 0x4f, 0x42, - 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, - 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x15, 0x0a, 0x11, 0x4a, 0x4f, 0x42, 0x5f, 0x53, 0x54, 0x41, - 0x54, 0x55, 0x53, 0x5f, 0x51, 0x55, 0x45, 0x55, 0x45, 0x44, 0x10, 0x01, 0x12, 0x18, 0x0a, 0x14, - 0x4a, 0x4f, 0x42, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x45, 0x58, 0x45, 0x43, 0x55, - 0x54, 0x49, 0x4e, 0x47, 0x10, 0x02, 0x12, 0x15, 0x0a, 0x11, 0x4a, 0x4f, 0x42, 0x5f, 0x53, 0x54, - 0x41, 0x54, 0x55, 0x53, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x03, 0x12, 0x17, 0x0a, - 0x13, 0x4a, 0x4f, 0x42, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x43, 0x41, 0x4e, 0x43, - 0x45, 0x4c, 0x45, 0x44, 0x10, 0x04, 0x12, 0x16, 0x0a, 0x12, 0x4a, 0x4f, 0x42, 0x5f, 0x53, 0x54, - 0x41, 0x54, 0x55, 0x53, 0x5f, 0x53, 0x55, 0x43, 0x43, 0x45, 0x53, 0x53, 0x10, 0x05, 0x2a, 0xc3, - 0x01, 0x0a, 0x13, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x4a, 0x6f, 0x62, 0x73, 0x53, 0x65, - 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x25, 0x0a, 0x21, 0x53, 0x54, 0x4f, 0x52, 0x41, 0x47, - 0x45, 0x5f, 0x4a, 0x4f, 0x42, 0x53, 0x5f, 0x53, 0x45, 0x4c, 0x45, 0x43, 0x54, 0x4f, 0x52, 0x5f, - 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x1d, 0x0a, - 0x19, 0x53, 0x54, 0x4f, 0x52, 0x41, 0x47, 0x45, 0x5f, 0x4a, 0x4f, 0x42, 0x53, 0x5f, 0x53, 0x45, - 0x4c, 0x45, 0x43, 0x54, 0x4f, 0x52, 0x5f, 0x41, 0x4c, 0x4c, 0x10, 0x01, 0x12, 0x20, 0x0a, 0x1c, - 0x53, 0x54, 0x4f, 0x52, 0x41, 0x47, 0x45, 0x5f, 0x4a, 0x4f, 0x42, 0x53, 0x5f, 0x53, 0x45, 0x4c, - 0x45, 0x43, 0x54, 0x4f, 0x52, 0x5f, 0x51, 0x55, 0x45, 0x55, 0x45, 0x44, 0x10, 0x02, 0x12, 0x23, - 0x0a, 0x1f, 0x53, 0x54, 0x4f, 0x52, 0x41, 0x47, 0x45, 0x5f, 0x4a, 0x4f, 0x42, 0x53, 0x5f, 0x53, - 0x45, 0x4c, 0x45, 0x43, 0x54, 0x4f, 0x52, 0x5f, 0x45, 0x58, 0x45, 0x43, 0x55, 0x54, 0x49, 0x4e, - 0x47, 0x10, 0x03, 0x12, 0x1f, 0x0a, 0x1b, 0x53, 0x54, 0x4f, 0x52, 0x41, 0x47, 0x45, 0x5f, 0x4a, - 0x4f, 0x42, 0x53, 0x5f, 0x53, 0x45, 0x4c, 0x45, 0x43, 0x54, 0x4f, 0x52, 0x5f, 0x46, 0x49, 0x4e, - 0x41, 0x4c, 0x10, 0x04, 0x32, 0xeb, 0x16, 0x0a, 0x0b, 0x55, 0x73, 0x65, 0x72, 0x53, 0x65, 0x72, - 0x76, 0x69, 0x63, 0x65, 0x12, 0x58, 0x0a, 0x09, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x49, 0x6e, 0x66, - 0x6f, 0x12, 0x23, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, - 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x49, 0x6e, 0x66, 0x6f, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, - 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, - 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x67, - 0x0a, 0x0e, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, - 0x12, 0x28, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, - 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, - 0x69, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x70, 0x6f, 0x77, - 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, - 0x73, 0x65, 0x72, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x79, 0x0a, 0x14, 0x44, 0x65, 0x66, 0x61, 0x75, - 0x6c, 0x74, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, - 0x2e, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, - 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x53, 0x74, 0x6f, 0x72, 0x61, - 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x2f, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, - 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x53, 0x74, 0x6f, 0x72, 0x61, - 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x00, 0x12, 0x82, 0x01, 0x0a, 0x17, 0x53, 0x65, 0x74, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, - 0x74, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x31, + 0x52, 0x11, 0x64, 0x61, 0x74, 0x61, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x53, 0x74, + 0x61, 0x72, 0x74, 0x12, 0x46, 0x0a, 0x11, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x74, 0x72, 0x61, 0x6e, + 0x73, 0x66, 0x65, 0x72, 0x5f, 0x65, 0x6e, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0f, 0x64, 0x61, 0x74, 0x61, + 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x45, 0x6e, 0x64, 0x12, 0x3f, 0x0a, 0x0d, 0x73, + 0x65, 0x61, 0x6c, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x09, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0c, + 0x73, 0x65, 0x61, 0x6c, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x72, 0x74, 0x12, 0x3b, 0x0a, 0x0b, + 0x73, 0x65, 0x61, 0x6c, 0x69, 0x6e, 0x67, 0x5f, 0x65, 0x6e, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x73, + 0x65, 0x61, 0x6c, 0x69, 0x6e, 0x67, 0x45, 0x6e, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x65, 0x72, 0x72, + 0x5f, 0x6d, 0x73, 0x67, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x65, 0x72, 0x72, 0x4d, + 0x73, 0x67, 0x12, 0x39, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, + 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, + 0x6d, 0x70, 0x52, 0x09, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x22, 0x80, 0x02, + 0x0a, 0x11, 0x52, 0x65, 0x74, 0x72, 0x69, 0x65, 0x76, 0x61, 0x6c, 0x44, 0x65, 0x61, 0x6c, 0x49, + 0x6e, 0x66, 0x6f, 0x12, 0x19, 0x0a, 0x08, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x63, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x72, 0x6f, 0x6f, 0x74, 0x43, 0x69, 0x64, 0x12, 0x12, + 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x73, 0x69, + 0x7a, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x69, 0x6e, 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x6d, 0x69, 0x6e, 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, + 0x29, 0x0a, 0x10, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, + 0x76, 0x61, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x70, 0x61, 0x79, 0x6d, 0x65, + 0x6e, 0x74, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x3a, 0x0a, 0x19, 0x70, 0x61, + 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x5f, 0x69, + 0x6e, 0x63, 0x72, 0x65, 0x61, 0x73, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x17, 0x70, + 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x49, 0x6e, + 0x63, 0x72, 0x65, 0x61, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x69, 0x6e, 0x65, 0x72, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6d, 0x69, 0x6e, 0x65, 0x72, 0x12, 0x22, 0x0a, 0x0d, + 0x6d, 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6d, 0x69, 0x6e, 0x65, 0x72, 0x50, 0x65, 0x65, 0x72, 0x49, 0x64, + 0x22, 0xa5, 0x03, 0x0a, 0x13, 0x52, 0x65, 0x74, 0x72, 0x69, 0x65, 0x76, 0x61, 0x6c, 0x44, 0x65, + 0x61, 0x6c, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 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, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x41, 0x0a, 0x09, 0x64, 0x65, 0x61, 0x6c, 0x5f, 0x69, + 0x6e, 0x66, 0x6f, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x70, 0x6f, 0x77, 0x65, + 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, + 0x74, 0x72, 0x69, 0x65, 0x76, 0x61, 0x6c, 0x44, 0x65, 0x61, 0x6c, 0x49, 0x6e, 0x66, 0x6f, 0x52, + 0x08, 0x64, 0x65, 0x61, 0x6c, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x4a, 0x0a, 0x13, 0x64, 0x61, 0x74, + 0x61, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, + 0x6d, 0x70, 0x52, 0x11, 0x64, 0x61, 0x74, 0x61, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, + 0x53, 0x74, 0x61, 0x72, 0x74, 0x12, 0x46, 0x0a, 0x11, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x74, 0x72, + 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x5f, 0x65, 0x6e, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0f, 0x64, 0x61, + 0x74, 0x61, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x45, 0x6e, 0x64, 0x12, 0x17, 0x0a, + 0x07, 0x65, 0x72, 0x72, 0x5f, 0x6d, 0x73, 0x67, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x65, 0x72, 0x72, 0x4d, 0x73, 0x67, 0x12, 0x39, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x64, 0x5f, 0x61, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, + 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, + 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, + 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x72, 0x65, 0x63, 0x65, 0x69, + 0x76, 0x65, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, 0x62, 0x79, 0x74, 0x65, 0x73, + 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x64, 0x2a, 0xa0, 0x01, 0x0a, 0x09, 0x4a, 0x6f, 0x62, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1a, 0x0a, 0x16, 0x4a, 0x4f, 0x42, 0x5f, 0x53, 0x54, + 0x41, 0x54, 0x55, 0x53, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, + 0x10, 0x00, 0x12, 0x15, 0x0a, 0x11, 0x4a, 0x4f, 0x42, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, + 0x5f, 0x51, 0x55, 0x45, 0x55, 0x45, 0x44, 0x10, 0x01, 0x12, 0x18, 0x0a, 0x14, 0x4a, 0x4f, 0x42, + 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x45, 0x58, 0x45, 0x43, 0x55, 0x54, 0x49, 0x4e, + 0x47, 0x10, 0x02, 0x12, 0x15, 0x0a, 0x11, 0x4a, 0x4f, 0x42, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, + 0x53, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x03, 0x12, 0x17, 0x0a, 0x13, 0x4a, 0x4f, + 0x42, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x43, 0x41, 0x4e, 0x43, 0x45, 0x4c, 0x45, + 0x44, 0x10, 0x04, 0x12, 0x16, 0x0a, 0x12, 0x4a, 0x4f, 0x42, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, + 0x53, 0x5f, 0x53, 0x55, 0x43, 0x43, 0x45, 0x53, 0x53, 0x10, 0x05, 0x2a, 0xc3, 0x01, 0x0a, 0x13, + 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x4a, 0x6f, 0x62, 0x73, 0x53, 0x65, 0x6c, 0x65, 0x63, + 0x74, 0x6f, 0x72, 0x12, 0x25, 0x0a, 0x21, 0x53, 0x54, 0x4f, 0x52, 0x41, 0x47, 0x45, 0x5f, 0x4a, + 0x4f, 0x42, 0x53, 0x5f, 0x53, 0x45, 0x4c, 0x45, 0x43, 0x54, 0x4f, 0x52, 0x5f, 0x55, 0x4e, 0x53, + 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x1d, 0x0a, 0x19, 0x53, 0x54, + 0x4f, 0x52, 0x41, 0x47, 0x45, 0x5f, 0x4a, 0x4f, 0x42, 0x53, 0x5f, 0x53, 0x45, 0x4c, 0x45, 0x43, + 0x54, 0x4f, 0x52, 0x5f, 0x41, 0x4c, 0x4c, 0x10, 0x01, 0x12, 0x20, 0x0a, 0x1c, 0x53, 0x54, 0x4f, + 0x52, 0x41, 0x47, 0x45, 0x5f, 0x4a, 0x4f, 0x42, 0x53, 0x5f, 0x53, 0x45, 0x4c, 0x45, 0x43, 0x54, + 0x4f, 0x52, 0x5f, 0x51, 0x55, 0x45, 0x55, 0x45, 0x44, 0x10, 0x02, 0x12, 0x23, 0x0a, 0x1f, 0x53, + 0x54, 0x4f, 0x52, 0x41, 0x47, 0x45, 0x5f, 0x4a, 0x4f, 0x42, 0x53, 0x5f, 0x53, 0x45, 0x4c, 0x45, + 0x43, 0x54, 0x4f, 0x52, 0x5f, 0x45, 0x58, 0x45, 0x43, 0x55, 0x54, 0x49, 0x4e, 0x47, 0x10, 0x03, + 0x12, 0x1f, 0x0a, 0x1b, 0x53, 0x54, 0x4f, 0x52, 0x41, 0x47, 0x45, 0x5f, 0x4a, 0x4f, 0x42, 0x53, + 0x5f, 0x53, 0x45, 0x4c, 0x45, 0x43, 0x54, 0x4f, 0x52, 0x5f, 0x46, 0x49, 0x4e, 0x41, 0x4c, 0x10, + 0x04, 0x32, 0xeb, 0x16, 0x0a, 0x0b, 0x55, 0x73, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x12, 0x58, 0x0a, 0x09, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x23, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, - 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x53, 0x74, 0x6f, - 0x72, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x32, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, - 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, - 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x73, 0x0a, 0x12, 0x41, 0x70, 0x70, 0x6c, 0x79, - 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2c, 0x2e, + 0x76, 0x31, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, + 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x49, 0x6e, 0x66, + 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x67, 0x0a, 0x0e, 0x55, + 0x73, 0x65, 0x72, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, 0x28, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, - 0x31, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x43, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x70, 0x6f, + 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, + 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, + 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x00, 0x12, 0x79, 0x0a, 0x14, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x53, + 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2e, 0x2e, 0x70, + 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, + 0x2e, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x43, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x70, + 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, + 0x2e, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x43, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, + 0x82, 0x01, 0x0a, 0x17, 0x53, 0x65, 0x74, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x53, 0x74, + 0x6f, 0x72, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x31, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, - 0x41, 0x70, 0x70, 0x6c, 0x79, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4f, 0x0a, 0x06, - 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x12, 0x20, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, - 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, - 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, - 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6d, - 0x6f, 0x76, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4e, 0x0a, - 0x05, 0x53, 0x74, 0x61, 0x67, 0x65, 0x12, 0x1f, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, + 0x53, 0x65, 0x74, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, + 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x32, + 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, + 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x53, 0x74, 0x6f, + 0x72, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x00, 0x12, 0x73, 0x0a, 0x12, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x53, 0x74, 0x6f, + 0x72, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2c, 0x2e, 0x70, 0x6f, 0x77, + 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, + 0x70, 0x70, 0x6c, 0x79, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, + 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x70, 0x70, + 0x6c, 0x79, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4f, 0x0a, 0x06, 0x52, 0x65, 0x6d, + 0x6f, 0x76, 0x65, 0x12, 0x20, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, + 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, + 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4e, 0x0a, 0x05, 0x53, 0x74, + 0x61, 0x67, 0x65, 0x12, 0x1f, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, + 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, + 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x61, 0x67, 0x65, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x28, 0x01, 0x12, 0x55, 0x0a, 0x08, 0x53, 0x74, + 0x61, 0x67, 0x65, 0x43, 0x69, 0x64, 0x12, 0x22, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x61, 0x67, 0x65, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, - 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x61, 0x67, - 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x28, 0x01, 0x12, 0x55, 0x0a, - 0x08, 0x53, 0x74, 0x61, 0x67, 0x65, 0x43, 0x69, 0x64, 0x12, 0x22, 0x2e, 0x70, 0x6f, 0x77, 0x65, - 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, - 0x61, 0x67, 0x65, 0x43, 0x69, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, + 0x43, 0x69, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x70, 0x6f, 0x77, + 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, + 0x74, 0x61, 0x67, 0x65, 0x43, 0x69, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x00, 0x12, 0x5e, 0x0a, 0x0b, 0x52, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x44, 0x61, 0x74, 0x61, + 0x12, 0x25, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, + 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x44, 0x61, 0x74, 0x61, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, + 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x70, 0x6c, + 0x61, 0x63, 0x65, 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x00, 0x12, 0x48, 0x0a, 0x03, 0x47, 0x65, 0x74, 0x12, 0x1d, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, + 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, + 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12, 0x5a, 0x0a, 0x09, 0x57, + 0x61, 0x74, 0x63, 0x68, 0x4c, 0x6f, 0x67, 0x73, 0x12, 0x23, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, + 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x57, 0x61, 0x74, + 0x63, 0x68, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, - 0x31, 0x2e, 0x53, 0x74, 0x61, 0x67, 0x65, 0x43, 0x69, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x00, 0x12, 0x5e, 0x0a, 0x0b, 0x52, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x44, - 0x61, 0x74, 0x61, 0x12, 0x25, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, - 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x44, - 0x61, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x70, 0x6f, 0x77, - 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, - 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x00, 0x12, 0x48, 0x0a, 0x03, 0x47, 0x65, 0x74, 0x12, 0x1d, 0x2e, 0x70, 0x6f, + 0x31, 0x2e, 0x57, 0x61, 0x74, 0x63, 0x68, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12, 0x5b, 0x0a, 0x0a, 0x43, 0x69, 0x64, 0x53, 0x75, + 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x24, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, + 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x69, 0x64, 0x53, 0x75, 0x6d, + 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, - 0x47, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x70, 0x6f, 0x77, - 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, - 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12, 0x5a, - 0x0a, 0x09, 0x57, 0x61, 0x74, 0x63, 0x68, 0x4c, 0x6f, 0x67, 0x73, 0x12, 0x23, 0x2e, 0x70, 0x6f, - 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, - 0x57, 0x61, 0x74, 0x63, 0x68, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x24, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, - 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x57, 0x61, 0x74, 0x63, 0x68, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12, 0x5b, 0x0a, 0x0a, 0x43, 0x69, - 0x64, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x24, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, - 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x69, 0x64, - 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, - 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, - 0x76, 0x31, 0x2e, 0x43, 0x69, 0x64, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x52, 0x0a, 0x07, 0x43, 0x69, 0x64, 0x49, 0x6e, - 0x66, 0x6f, 0x12, 0x21, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, + 0x43, 0x69, 0x64, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x00, 0x12, 0x52, 0x0a, 0x07, 0x43, 0x69, 0x64, 0x49, 0x6e, 0x66, 0x6f, 0x12, + 0x21, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, + 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x69, 0x64, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x69, 0x64, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, - 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x69, 0x64, 0x49, 0x6e, 0x66, - 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x52, 0x0a, 0x07, 0x42, - 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x21, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x52, 0x0a, 0x07, 0x42, 0x61, 0x6c, 0x61, + 0x6e, 0x63, 0x65, 0x12, 0x21, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, + 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x6c, 0x61, 0x6e, - 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x70, 0x6f, 0x77, 0x65, - 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, - 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, - 0x5b, 0x0a, 0x0a, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x24, 0x2e, - 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, - 0x31, 0x2e, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, - 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, - 0x73, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x58, 0x0a, 0x09, - 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x12, 0x23, 0x2e, 0x70, 0x6f, 0x77, 0x65, - 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, - 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, + 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x5b, 0x0a, 0x0a, + 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x24, 0x2e, 0x70, 0x6f, 0x77, + 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4e, + 0x65, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x25, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, + 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x58, 0x0a, 0x09, 0x41, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x12, 0x23, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, + 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, + 0x73, 0x73, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x70, 0x6f, + 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, + 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x00, 0x12, 0x52, 0x0a, 0x07, 0x53, 0x65, 0x6e, 0x64, 0x46, 0x69, 0x6c, 0x12, 0x21, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, - 0x76, 0x31, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x52, 0x0a, 0x07, 0x53, 0x65, 0x6e, 0x64, 0x46, 0x69, - 0x6c, 0x12, 0x21, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, - 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x46, 0x69, 0x6c, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, - 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x46, 0x69, 0x6c, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x5e, 0x0a, 0x0b, 0x53, 0x69, - 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x25, 0x2e, 0x70, 0x6f, 0x77, 0x65, - 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x69, - 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x26, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, - 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x64, 0x0a, 0x0d, 0x56, 0x65, - 0x72, 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x27, 0x2e, 0x70, 0x6f, + 0x76, 0x31, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x46, 0x69, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x22, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, + 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x46, 0x69, 0x6c, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x5e, 0x0a, 0x0b, 0x53, 0x69, 0x67, 0x6e, 0x4d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x25, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, + 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x4d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, + 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, + 0x31, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x64, 0x0a, 0x0d, 0x56, 0x65, 0x72, 0x69, 0x66, + 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x27, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, + 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x65, 0x72, + 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x28, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, + 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x5e, 0x0a, + 0x0b, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x25, 0x2e, 0x70, + 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, + 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, + 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x49, + 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x6a, 0x0a, + 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x49, 0x6e, 0x66, 0x6f, + 0x12, 0x29, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, + 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, + 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, - 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, - 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d, - 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, - 0x12, 0x5e, 0x0a, 0x0b, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, - 0x25, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, - 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, - 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, - 0x67, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, - 0x12, 0x6a, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x49, - 0x6e, 0x66, 0x6f, 0x12, 0x29, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, - 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x74, 0x6f, 0x72, - 0x61, 0x67, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, + 0x4c, 0x69, 0x73, 0x74, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x5b, 0x0a, 0x0a, 0x53, 0x74, 0x6f, + 0x72, 0x61, 0x67, 0x65, 0x4a, 0x6f, 0x62, 0x12, 0x24, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, + 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x6f, 0x72, + 0x61, 0x67, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, + 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, + 0x31, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x76, 0x0a, 0x13, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, + 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x46, 0x6f, 0x72, 0x4a, 0x6f, 0x62, 0x12, 0x2d, 0x2e, + 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, + 0x31, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x46, + 0x6f, 0x72, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x70, + 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, + 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x46, 0x6f, + 0x72, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x6a, + 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x4a, 0x6f, 0x62, + 0x73, 0x12, 0x29, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, + 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, + 0x65, 0x4a, 0x6f, 0x62, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x70, + 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, + 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x4a, 0x6f, 0x62, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x73, 0x0a, 0x12, 0x53, 0x74, + 0x6f, 0x72, 0x61, 0x67, 0x65, 0x4a, 0x6f, 0x62, 0x73, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, + 0x12, 0x2c, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, + 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x4a, 0x6f, 0x62, 0x73, + 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, - 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x49, 0x6e, - 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x5b, 0x0a, 0x0a, - 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x4a, 0x6f, 0x62, 0x12, 0x24, 0x2e, 0x70, 0x6f, 0x77, - 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, - 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x25, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, - 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x4a, 0x6f, 0x62, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x76, 0x0a, 0x13, 0x53, 0x74, 0x6f, - 0x72, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x46, 0x6f, 0x72, 0x4a, 0x6f, 0x62, - 0x12, 0x2d, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, - 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x46, 0x6f, 0x72, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x2e, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, - 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x46, 0x6f, 0x72, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, - 0x00, 0x12, 0x6a, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, - 0x4a, 0x6f, 0x62, 0x73, 0x12, 0x29, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, - 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x74, 0x6f, + 0x76, 0x31, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x4a, 0x6f, 0x62, 0x73, 0x53, 0x75, + 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, + 0x6f, 0x0a, 0x10, 0x57, 0x61, 0x74, 0x63, 0x68, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x4a, + 0x6f, 0x62, 0x73, 0x12, 0x2a, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, + 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x57, 0x61, 0x74, 0x63, 0x68, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x4a, 0x6f, 0x62, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x2a, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, - 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x4a, - 0x6f, 0x62, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x73, 0x0a, - 0x12, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x4a, 0x6f, 0x62, 0x73, 0x53, 0x75, 0x6d, 0x6d, - 0x61, 0x72, 0x79, 0x12, 0x2c, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, - 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x4a, - 0x6f, 0x62, 0x73, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x2d, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, - 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x4a, 0x6f, 0x62, - 0x73, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x00, 0x12, 0x6f, 0x0a, 0x10, 0x57, 0x61, 0x74, 0x63, 0x68, 0x53, 0x74, 0x6f, 0x72, 0x61, - 0x67, 0x65, 0x4a, 0x6f, 0x62, 0x73, 0x12, 0x2a, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, - 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x57, 0x61, 0x74, 0x63, 0x68, - 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x4a, 0x6f, 0x62, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, - 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x57, 0x61, 0x74, 0x63, 0x68, 0x53, 0x74, 0x6f, 0x72, - 0x61, 0x67, 0x65, 0x4a, 0x6f, 0x62, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, - 0x00, 0x30, 0x01, 0x12, 0x6d, 0x0a, 0x10, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x53, 0x74, 0x6f, - 0x72, 0x61, 0x67, 0x65, 0x4a, 0x6f, 0x62, 0x12, 0x2a, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, - 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x61, 0x6e, 0x63, - 0x65, 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, - 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x53, 0x74, - 0x6f, 0x72, 0x61, 0x67, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x00, 0x12, 0x73, 0x0a, 0x12, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x44, 0x65, 0x61, - 0x6c, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x12, 0x2c, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, - 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x6f, - 0x72, 0x61, 0x67, 0x65, 0x44, 0x65, 0x61, 0x6c, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, - 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, - 0x67, 0x65, 0x44, 0x65, 0x61, 0x6c, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x79, 0x0a, 0x14, 0x52, 0x65, 0x74, 0x72, 0x69, - 0x65, 0x76, 0x61, 0x6c, 0x44, 0x65, 0x61, 0x6c, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x12, - 0x2e, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, - 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x74, 0x72, 0x69, 0x65, 0x76, 0x61, 0x6c, 0x44, 0x65, 0x61, - 0x6c, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x2f, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, - 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x74, 0x72, 0x69, 0x65, 0x76, 0x61, 0x6c, 0x44, 0x65, 0x61, - 0x6c, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x00, 0x42, 0x44, 0x5a, 0x42, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, - 0x2f, 0x74, 0x65, 0x78, 0x74, 0x69, 0x6c, 0x65, 0x69, 0x6f, 0x2f, 0x70, 0x6f, 0x77, 0x65, 0x72, - 0x67, 0x61, 0x74, 0x65, 0x2f, 0x76, 0x32, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x67, 0x65, 0x6e, 0x2f, - 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x2f, 0x76, - 0x31, 0x3b, 0x75, 0x73, 0x65, 0x72, 0x50, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x2b, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, + 0x2e, 0x76, 0x31, 0x2e, 0x57, 0x61, 0x74, 0x63, 0x68, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, + 0x4a, 0x6f, 0x62, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x30, 0x01, + 0x12, 0x6d, 0x0a, 0x10, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, + 0x65, 0x4a, 0x6f, 0x62, 0x12, 0x2a, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, + 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x53, + 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x2b, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, + 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x61, + 0x67, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, + 0x73, 0x0a, 0x12, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x44, 0x65, 0x61, 0x6c, 0x52, 0x65, + 0x63, 0x6f, 0x72, 0x64, 0x73, 0x12, 0x2c, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, + 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, + 0x65, 0x44, 0x65, 0x61, 0x6c, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, + 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x44, + 0x65, 0x61, 0x6c, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x00, 0x12, 0x79, 0x0a, 0x14, 0x52, 0x65, 0x74, 0x72, 0x69, 0x65, 0x76, 0x61, + 0x6c, 0x44, 0x65, 0x61, 0x6c, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x12, 0x2e, 0x2e, 0x70, + 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, + 0x2e, 0x52, 0x65, 0x74, 0x72, 0x69, 0x65, 0x76, 0x61, 0x6c, 0x44, 0x65, 0x61, 0x6c, 0x52, 0x65, + 0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x70, + 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, + 0x2e, 0x52, 0x65, 0x74, 0x72, 0x69, 0x65, 0x76, 0x61, 0x6c, 0x44, 0x65, 0x61, 0x6c, 0x52, 0x65, + 0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, + 0x44, 0x5a, 0x42, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x74, 0x65, + 0x78, 0x74, 0x69, 0x6c, 0x65, 0x69, 0x6f, 0x2f, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, + 0x65, 0x2f, 0x76, 0x32, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x70, 0x6f, 0x77, + 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x2f, 0x76, 0x31, 0x3b, 0x75, + 0x73, 0x65, 0x72, 0x50, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -5879,7 +6261,7 @@ func file_powergate_user_v1_user_proto_rawDescGZIP() []byte { } var file_powergate_user_v1_user_proto_enumTypes = make([]protoimpl.EnumInfo, 2) -var file_powergate_user_v1_user_proto_msgTypes = make([]protoimpl.MessageInfo, 83) +var file_powergate_user_v1_user_proto_msgTypes = make([]protoimpl.MessageInfo, 89) var file_powergate_user_v1_user_proto_goTypes = []interface{}{ (JobStatus)(0), // 0: powergate.user.v1.JobStatus (StorageJobsSelector)(0), // 1: powergate.user.v1.StorageJobsSelector @@ -5948,136 +6330,148 @@ var file_powergate_user_v1_user_proto_goTypes = []interface{}{ (*FilRenew)(nil), // 64: powergate.user.v1.FilRenew (*FilConfig)(nil), // 65: powergate.user.v1.FilConfig (*ColdConfig)(nil), // 66: powergate.user.v1.ColdConfig - (*StorageConfig)(nil), // 67: powergate.user.v1.StorageConfig - (*IpfsHotInfo)(nil), // 68: powergate.user.v1.IpfsHotInfo - (*HotInfo)(nil), // 69: powergate.user.v1.HotInfo - (*FilStorage)(nil), // 70: powergate.user.v1.FilStorage - (*FilInfo)(nil), // 71: powergate.user.v1.FilInfo - (*ColdInfo)(nil), // 72: powergate.user.v1.ColdInfo - (*StorageInfo)(nil), // 73: powergate.user.v1.StorageInfo - (*CidInfo)(nil), // 74: powergate.user.v1.CidInfo - (*DealInfo)(nil), // 75: powergate.user.v1.DealInfo - (*StorageJob)(nil), // 76: powergate.user.v1.StorageJob - (*DealError)(nil), // 77: powergate.user.v1.DealError - (*LogEntry)(nil), // 78: powergate.user.v1.LogEntry - (*DealRecordsConfig)(nil), // 79: powergate.user.v1.DealRecordsConfig - (*StorageDealInfo)(nil), // 80: powergate.user.v1.StorageDealInfo - (*StorageDealRecord)(nil), // 81: powergate.user.v1.StorageDealRecord - (*RetrievalDealInfo)(nil), // 82: powergate.user.v1.RetrievalDealInfo - (*RetrievalDealRecord)(nil), // 83: powergate.user.v1.RetrievalDealRecord - (*AddrInfo_VerifiedClientInfo)(nil), // 84: powergate.user.v1.AddrInfo.VerifiedClientInfo - (*timestamppb.Timestamp)(nil), // 85: google.protobuf.Timestamp + (*WebhookAuthData)(nil), // 67: powergate.user.v1.WebhookAuthData + (*WebhookAuthentication)(nil), // 68: powergate.user.v1.WebhookAuthentication + (*Webhook)(nil), // 69: powergate.user.v1.Webhook + (*WebhookAlert)(nil), // 70: powergate.user.v1.WebhookAlert + (*WebhookConfiguration)(nil), // 71: powergate.user.v1.WebhookConfiguration + (*NotificationConfig)(nil), // 72: powergate.user.v1.NotificationConfig + (*StorageConfig)(nil), // 73: powergate.user.v1.StorageConfig + (*IpfsHotInfo)(nil), // 74: powergate.user.v1.IpfsHotInfo + (*HotInfo)(nil), // 75: powergate.user.v1.HotInfo + (*FilStorage)(nil), // 76: powergate.user.v1.FilStorage + (*FilInfo)(nil), // 77: powergate.user.v1.FilInfo + (*ColdInfo)(nil), // 78: powergate.user.v1.ColdInfo + (*StorageInfo)(nil), // 79: powergate.user.v1.StorageInfo + (*CidInfo)(nil), // 80: powergate.user.v1.CidInfo + (*DealInfo)(nil), // 81: powergate.user.v1.DealInfo + (*StorageJob)(nil), // 82: powergate.user.v1.StorageJob + (*DealError)(nil), // 83: powergate.user.v1.DealError + (*LogEntry)(nil), // 84: powergate.user.v1.LogEntry + (*DealRecordsConfig)(nil), // 85: powergate.user.v1.DealRecordsConfig + (*StorageDealInfo)(nil), // 86: powergate.user.v1.StorageDealInfo + (*StorageDealRecord)(nil), // 87: powergate.user.v1.StorageDealRecord + (*RetrievalDealInfo)(nil), // 88: powergate.user.v1.RetrievalDealInfo + (*RetrievalDealRecord)(nil), // 89: powergate.user.v1.RetrievalDealRecord + (*AddrInfo_VerifiedClientInfo)(nil), // 90: powergate.user.v1.AddrInfo.VerifiedClientInfo + (*timestamppb.Timestamp)(nil), // 91: google.protobuf.Timestamp } var file_powergate_user_v1_user_proto_depIdxs = []int32{ - 67, // 0: powergate.user.v1.DefaultStorageConfigResponse.default_storage_config:type_name -> powergate.user.v1.StorageConfig - 67, // 1: powergate.user.v1.SetDefaultStorageConfigRequest.config:type_name -> powergate.user.v1.StorageConfig - 67, // 2: powergate.user.v1.ApplyStorageConfigRequest.config:type_name -> powergate.user.v1.StorageConfig - 78, // 3: powergate.user.v1.WatchLogsResponse.log_entry:type_name -> powergate.user.v1.LogEntry + 73, // 0: powergate.user.v1.DefaultStorageConfigResponse.default_storage_config:type_name -> powergate.user.v1.StorageConfig + 73, // 1: powergate.user.v1.SetDefaultStorageConfigRequest.config:type_name -> powergate.user.v1.StorageConfig + 73, // 2: powergate.user.v1.ApplyStorageConfigRequest.config:type_name -> powergate.user.v1.StorageConfig + 84, // 3: powergate.user.v1.WatchLogsResponse.log_entry:type_name -> powergate.user.v1.LogEntry 61, // 4: powergate.user.v1.CidSummaryResponse.cid_summary:type_name -> powergate.user.v1.CidSummary - 74, // 5: powergate.user.v1.CidInfoResponse.cid_info:type_name -> powergate.user.v1.CidInfo + 80, // 5: powergate.user.v1.CidInfoResponse.cid_info:type_name -> powergate.user.v1.CidInfo 60, // 6: powergate.user.v1.AddressesResponse.addresses:type_name -> powergate.user.v1.AddrInfo - 73, // 7: powergate.user.v1.StorageInfoResponse.storage_info:type_name -> powergate.user.v1.StorageInfo - 73, // 8: powergate.user.v1.ListStorageInfoResponse.storage_info:type_name -> powergate.user.v1.StorageInfo - 76, // 9: powergate.user.v1.StorageJobResponse.storage_job:type_name -> powergate.user.v1.StorageJob - 67, // 10: powergate.user.v1.StorageConfigForJobResponse.storage_config:type_name -> powergate.user.v1.StorageConfig + 79, // 7: powergate.user.v1.StorageInfoResponse.storage_info:type_name -> powergate.user.v1.StorageInfo + 79, // 8: powergate.user.v1.ListStorageInfoResponse.storage_info:type_name -> powergate.user.v1.StorageInfo + 82, // 9: powergate.user.v1.StorageJobResponse.storage_job:type_name -> powergate.user.v1.StorageJob + 73, // 10: powergate.user.v1.StorageConfigForJobResponse.storage_config:type_name -> powergate.user.v1.StorageConfig 1, // 11: powergate.user.v1.ListStorageJobsRequest.selector:type_name -> powergate.user.v1.StorageJobsSelector - 76, // 12: powergate.user.v1.ListStorageJobsResponse.storage_jobs:type_name -> powergate.user.v1.StorageJob - 76, // 13: powergate.user.v1.WatchStorageJobsResponse.storage_job:type_name -> powergate.user.v1.StorageJob - 79, // 14: powergate.user.v1.StorageDealRecordsRequest.config:type_name -> powergate.user.v1.DealRecordsConfig - 81, // 15: powergate.user.v1.StorageDealRecordsResponse.records:type_name -> powergate.user.v1.StorageDealRecord - 79, // 16: powergate.user.v1.RetrievalDealRecordsRequest.config:type_name -> powergate.user.v1.DealRecordsConfig - 83, // 17: powergate.user.v1.RetrievalDealRecordsResponse.records:type_name -> powergate.user.v1.RetrievalDealRecord - 84, // 18: powergate.user.v1.AddrInfo.verified_client_info:type_name -> powergate.user.v1.AddrInfo.VerifiedClientInfo + 82, // 12: powergate.user.v1.ListStorageJobsResponse.storage_jobs:type_name -> powergate.user.v1.StorageJob + 82, // 13: powergate.user.v1.WatchStorageJobsResponse.storage_job:type_name -> powergate.user.v1.StorageJob + 85, // 14: powergate.user.v1.StorageDealRecordsRequest.config:type_name -> powergate.user.v1.DealRecordsConfig + 87, // 15: powergate.user.v1.StorageDealRecordsResponse.records:type_name -> powergate.user.v1.StorageDealRecord + 85, // 16: powergate.user.v1.RetrievalDealRecordsRequest.config:type_name -> powergate.user.v1.DealRecordsConfig + 89, // 17: powergate.user.v1.RetrievalDealRecordsResponse.records:type_name -> powergate.user.v1.RetrievalDealRecord + 90, // 18: powergate.user.v1.AddrInfo.verified_client_info:type_name -> powergate.user.v1.AddrInfo.VerifiedClientInfo 62, // 19: powergate.user.v1.HotConfig.ipfs:type_name -> powergate.user.v1.IpfsConfig 64, // 20: powergate.user.v1.FilConfig.renew:type_name -> powergate.user.v1.FilRenew 65, // 21: powergate.user.v1.ColdConfig.filecoin:type_name -> powergate.user.v1.FilConfig - 63, // 22: powergate.user.v1.StorageConfig.hot:type_name -> powergate.user.v1.HotConfig - 66, // 23: powergate.user.v1.StorageConfig.cold:type_name -> powergate.user.v1.ColdConfig - 68, // 24: powergate.user.v1.HotInfo.ipfs:type_name -> powergate.user.v1.IpfsHotInfo - 70, // 25: powergate.user.v1.FilInfo.proposals:type_name -> powergate.user.v1.FilStorage - 71, // 26: powergate.user.v1.ColdInfo.filecoin:type_name -> powergate.user.v1.FilInfo - 69, // 27: powergate.user.v1.StorageInfo.hot:type_name -> powergate.user.v1.HotInfo - 72, // 28: powergate.user.v1.StorageInfo.cold:type_name -> powergate.user.v1.ColdInfo - 67, // 29: powergate.user.v1.CidInfo.latest_pushed_storage_config:type_name -> powergate.user.v1.StorageConfig - 73, // 30: powergate.user.v1.CidInfo.current_storage_info:type_name -> powergate.user.v1.StorageInfo - 76, // 31: powergate.user.v1.CidInfo.queued_storage_jobs:type_name -> powergate.user.v1.StorageJob - 76, // 32: powergate.user.v1.CidInfo.executing_storage_job:type_name -> powergate.user.v1.StorageJob - 0, // 33: powergate.user.v1.StorageJob.status:type_name -> powergate.user.v1.JobStatus - 75, // 34: powergate.user.v1.StorageJob.deal_info:type_name -> powergate.user.v1.DealInfo - 77, // 35: powergate.user.v1.StorageJob.deal_errors:type_name -> powergate.user.v1.DealError - 80, // 36: powergate.user.v1.StorageDealRecord.deal_info:type_name -> powergate.user.v1.StorageDealInfo - 85, // 37: powergate.user.v1.StorageDealRecord.data_transfer_start:type_name -> google.protobuf.Timestamp - 85, // 38: powergate.user.v1.StorageDealRecord.data_transfer_end:type_name -> google.protobuf.Timestamp - 85, // 39: powergate.user.v1.StorageDealRecord.sealing_start:type_name -> google.protobuf.Timestamp - 85, // 40: powergate.user.v1.StorageDealRecord.sealing_end:type_name -> google.protobuf.Timestamp - 85, // 41: powergate.user.v1.StorageDealRecord.updated_at:type_name -> google.protobuf.Timestamp - 82, // 42: powergate.user.v1.RetrievalDealRecord.deal_info:type_name -> powergate.user.v1.RetrievalDealInfo - 85, // 43: powergate.user.v1.RetrievalDealRecord.data_transfer_start:type_name -> google.protobuf.Timestamp - 85, // 44: powergate.user.v1.RetrievalDealRecord.data_transfer_end:type_name -> google.protobuf.Timestamp - 85, // 45: powergate.user.v1.RetrievalDealRecord.updated_at:type_name -> google.protobuf.Timestamp - 2, // 46: powergate.user.v1.UserService.BuildInfo:input_type -> powergate.user.v1.BuildInfoRequest - 4, // 47: powergate.user.v1.UserService.UserIdentifier:input_type -> powergate.user.v1.UserIdentifierRequest - 6, // 48: powergate.user.v1.UserService.DefaultStorageConfig:input_type -> powergate.user.v1.DefaultStorageConfigRequest - 8, // 49: powergate.user.v1.UserService.SetDefaultStorageConfig:input_type -> powergate.user.v1.SetDefaultStorageConfigRequest - 14, // 50: powergate.user.v1.UserService.ApplyStorageConfig:input_type -> powergate.user.v1.ApplyStorageConfigRequest - 20, // 51: powergate.user.v1.UserService.Remove:input_type -> powergate.user.v1.RemoveRequest - 10, // 52: powergate.user.v1.UserService.Stage:input_type -> powergate.user.v1.StageRequest - 12, // 53: powergate.user.v1.UserService.StageCid:input_type -> powergate.user.v1.StageCidRequest - 16, // 54: powergate.user.v1.UserService.ReplaceData:input_type -> powergate.user.v1.ReplaceDataRequest - 18, // 55: powergate.user.v1.UserService.Get:input_type -> powergate.user.v1.GetRequest - 22, // 56: powergate.user.v1.UserService.WatchLogs:input_type -> powergate.user.v1.WatchLogsRequest - 24, // 57: powergate.user.v1.UserService.CidSummary:input_type -> powergate.user.v1.CidSummaryRequest - 26, // 58: powergate.user.v1.UserService.CidInfo:input_type -> powergate.user.v1.CidInfoRequest - 28, // 59: powergate.user.v1.UserService.Balance:input_type -> powergate.user.v1.BalanceRequest - 30, // 60: powergate.user.v1.UserService.NewAddress:input_type -> powergate.user.v1.NewAddressRequest - 32, // 61: powergate.user.v1.UserService.Addresses:input_type -> powergate.user.v1.AddressesRequest - 34, // 62: powergate.user.v1.UserService.SendFil:input_type -> powergate.user.v1.SendFilRequest - 36, // 63: powergate.user.v1.UserService.SignMessage:input_type -> powergate.user.v1.SignMessageRequest - 38, // 64: powergate.user.v1.UserService.VerifyMessage:input_type -> powergate.user.v1.VerifyMessageRequest - 40, // 65: powergate.user.v1.UserService.StorageInfo:input_type -> powergate.user.v1.StorageInfoRequest - 42, // 66: powergate.user.v1.UserService.ListStorageInfo:input_type -> powergate.user.v1.ListStorageInfoRequest - 46, // 67: powergate.user.v1.UserService.StorageJob:input_type -> powergate.user.v1.StorageJobRequest - 48, // 68: powergate.user.v1.UserService.StorageConfigForJob:input_type -> powergate.user.v1.StorageConfigForJobRequest - 50, // 69: powergate.user.v1.UserService.ListStorageJobs:input_type -> powergate.user.v1.ListStorageJobsRequest - 52, // 70: powergate.user.v1.UserService.StorageJobsSummary:input_type -> powergate.user.v1.StorageJobsSummaryRequest - 54, // 71: powergate.user.v1.UserService.WatchStorageJobs:input_type -> powergate.user.v1.WatchStorageJobsRequest - 44, // 72: powergate.user.v1.UserService.CancelStorageJob:input_type -> powergate.user.v1.CancelStorageJobRequest - 56, // 73: powergate.user.v1.UserService.StorageDealRecords:input_type -> powergate.user.v1.StorageDealRecordsRequest - 58, // 74: powergate.user.v1.UserService.RetrievalDealRecords:input_type -> powergate.user.v1.RetrievalDealRecordsRequest - 3, // 75: powergate.user.v1.UserService.BuildInfo:output_type -> powergate.user.v1.BuildInfoResponse - 5, // 76: powergate.user.v1.UserService.UserIdentifier:output_type -> powergate.user.v1.UserIdentifierResponse - 7, // 77: powergate.user.v1.UserService.DefaultStorageConfig:output_type -> powergate.user.v1.DefaultStorageConfigResponse - 9, // 78: powergate.user.v1.UserService.SetDefaultStorageConfig:output_type -> powergate.user.v1.SetDefaultStorageConfigResponse - 15, // 79: powergate.user.v1.UserService.ApplyStorageConfig:output_type -> powergate.user.v1.ApplyStorageConfigResponse - 21, // 80: powergate.user.v1.UserService.Remove:output_type -> powergate.user.v1.RemoveResponse - 11, // 81: powergate.user.v1.UserService.Stage:output_type -> powergate.user.v1.StageResponse - 13, // 82: powergate.user.v1.UserService.StageCid:output_type -> powergate.user.v1.StageCidResponse - 17, // 83: powergate.user.v1.UserService.ReplaceData:output_type -> powergate.user.v1.ReplaceDataResponse - 19, // 84: powergate.user.v1.UserService.Get:output_type -> powergate.user.v1.GetResponse - 23, // 85: powergate.user.v1.UserService.WatchLogs:output_type -> powergate.user.v1.WatchLogsResponse - 25, // 86: powergate.user.v1.UserService.CidSummary:output_type -> powergate.user.v1.CidSummaryResponse - 27, // 87: powergate.user.v1.UserService.CidInfo:output_type -> powergate.user.v1.CidInfoResponse - 29, // 88: powergate.user.v1.UserService.Balance:output_type -> powergate.user.v1.BalanceResponse - 31, // 89: powergate.user.v1.UserService.NewAddress:output_type -> powergate.user.v1.NewAddressResponse - 33, // 90: powergate.user.v1.UserService.Addresses:output_type -> powergate.user.v1.AddressesResponse - 35, // 91: powergate.user.v1.UserService.SendFil:output_type -> powergate.user.v1.SendFilResponse - 37, // 92: powergate.user.v1.UserService.SignMessage:output_type -> powergate.user.v1.SignMessageResponse - 39, // 93: powergate.user.v1.UserService.VerifyMessage:output_type -> powergate.user.v1.VerifyMessageResponse - 41, // 94: powergate.user.v1.UserService.StorageInfo:output_type -> powergate.user.v1.StorageInfoResponse - 43, // 95: powergate.user.v1.UserService.ListStorageInfo:output_type -> powergate.user.v1.ListStorageInfoResponse - 47, // 96: powergate.user.v1.UserService.StorageJob:output_type -> powergate.user.v1.StorageJobResponse - 49, // 97: powergate.user.v1.UserService.StorageConfigForJob:output_type -> powergate.user.v1.StorageConfigForJobResponse - 51, // 98: powergate.user.v1.UserService.ListStorageJobs:output_type -> powergate.user.v1.ListStorageJobsResponse - 53, // 99: powergate.user.v1.UserService.StorageJobsSummary:output_type -> powergate.user.v1.StorageJobsSummaryResponse - 55, // 100: powergate.user.v1.UserService.WatchStorageJobs:output_type -> powergate.user.v1.WatchStorageJobsResponse - 45, // 101: powergate.user.v1.UserService.CancelStorageJob:output_type -> powergate.user.v1.CancelStorageJobResponse - 57, // 102: powergate.user.v1.UserService.StorageDealRecords:output_type -> powergate.user.v1.StorageDealRecordsResponse - 59, // 103: powergate.user.v1.UserService.RetrievalDealRecords:output_type -> powergate.user.v1.RetrievalDealRecordsResponse - 75, // [75:104] is the sub-list for method output_type - 46, // [46:75] is the sub-list for method input_type - 46, // [46:46] is the sub-list for extension type_name - 46, // [46:46] is the sub-list for extension extendee - 0, // [0:46] is the sub-list for field type_name + 67, // 22: powergate.user.v1.WebhookAuthentication.data:type_name -> powergate.user.v1.WebhookAuthData + 68, // 23: powergate.user.v1.Webhook.authentication:type_name -> powergate.user.v1.WebhookAuthentication + 70, // 24: powergate.user.v1.WebhookConfiguration.alerts:type_name -> powergate.user.v1.WebhookAlert + 69, // 25: powergate.user.v1.NotificationConfig.webhook:type_name -> powergate.user.v1.Webhook + 71, // 26: powergate.user.v1.NotificationConfig.configuration:type_name -> powergate.user.v1.WebhookConfiguration + 63, // 27: powergate.user.v1.StorageConfig.hot:type_name -> powergate.user.v1.HotConfig + 66, // 28: powergate.user.v1.StorageConfig.cold:type_name -> powergate.user.v1.ColdConfig + 72, // 29: powergate.user.v1.StorageConfig.notifications:type_name -> powergate.user.v1.NotificationConfig + 74, // 30: powergate.user.v1.HotInfo.ipfs:type_name -> powergate.user.v1.IpfsHotInfo + 76, // 31: powergate.user.v1.FilInfo.proposals:type_name -> powergate.user.v1.FilStorage + 77, // 32: powergate.user.v1.ColdInfo.filecoin:type_name -> powergate.user.v1.FilInfo + 75, // 33: powergate.user.v1.StorageInfo.hot:type_name -> powergate.user.v1.HotInfo + 78, // 34: powergate.user.v1.StorageInfo.cold:type_name -> powergate.user.v1.ColdInfo + 73, // 35: powergate.user.v1.CidInfo.latest_pushed_storage_config:type_name -> powergate.user.v1.StorageConfig + 79, // 36: powergate.user.v1.CidInfo.current_storage_info:type_name -> powergate.user.v1.StorageInfo + 82, // 37: powergate.user.v1.CidInfo.queued_storage_jobs:type_name -> powergate.user.v1.StorageJob + 82, // 38: powergate.user.v1.CidInfo.executing_storage_job:type_name -> powergate.user.v1.StorageJob + 0, // 39: powergate.user.v1.StorageJob.status:type_name -> powergate.user.v1.JobStatus + 81, // 40: powergate.user.v1.StorageJob.deal_info:type_name -> powergate.user.v1.DealInfo + 83, // 41: powergate.user.v1.StorageJob.deal_errors:type_name -> powergate.user.v1.DealError + 86, // 42: powergate.user.v1.StorageDealRecord.deal_info:type_name -> powergate.user.v1.StorageDealInfo + 91, // 43: powergate.user.v1.StorageDealRecord.data_transfer_start:type_name -> google.protobuf.Timestamp + 91, // 44: powergate.user.v1.StorageDealRecord.data_transfer_end:type_name -> google.protobuf.Timestamp + 91, // 45: powergate.user.v1.StorageDealRecord.sealing_start:type_name -> google.protobuf.Timestamp + 91, // 46: powergate.user.v1.StorageDealRecord.sealing_end:type_name -> google.protobuf.Timestamp + 91, // 47: powergate.user.v1.StorageDealRecord.updated_at:type_name -> google.protobuf.Timestamp + 88, // 48: powergate.user.v1.RetrievalDealRecord.deal_info:type_name -> powergate.user.v1.RetrievalDealInfo + 91, // 49: powergate.user.v1.RetrievalDealRecord.data_transfer_start:type_name -> google.protobuf.Timestamp + 91, // 50: powergate.user.v1.RetrievalDealRecord.data_transfer_end:type_name -> google.protobuf.Timestamp + 91, // 51: powergate.user.v1.RetrievalDealRecord.updated_at:type_name -> google.protobuf.Timestamp + 2, // 52: powergate.user.v1.UserService.BuildInfo:input_type -> powergate.user.v1.BuildInfoRequest + 4, // 53: powergate.user.v1.UserService.UserIdentifier:input_type -> powergate.user.v1.UserIdentifierRequest + 6, // 54: powergate.user.v1.UserService.DefaultStorageConfig:input_type -> powergate.user.v1.DefaultStorageConfigRequest + 8, // 55: powergate.user.v1.UserService.SetDefaultStorageConfig:input_type -> powergate.user.v1.SetDefaultStorageConfigRequest + 14, // 56: powergate.user.v1.UserService.ApplyStorageConfig:input_type -> powergate.user.v1.ApplyStorageConfigRequest + 20, // 57: powergate.user.v1.UserService.Remove:input_type -> powergate.user.v1.RemoveRequest + 10, // 58: powergate.user.v1.UserService.Stage:input_type -> powergate.user.v1.StageRequest + 12, // 59: powergate.user.v1.UserService.StageCid:input_type -> powergate.user.v1.StageCidRequest + 16, // 60: powergate.user.v1.UserService.ReplaceData:input_type -> powergate.user.v1.ReplaceDataRequest + 18, // 61: powergate.user.v1.UserService.Get:input_type -> powergate.user.v1.GetRequest + 22, // 62: powergate.user.v1.UserService.WatchLogs:input_type -> powergate.user.v1.WatchLogsRequest + 24, // 63: powergate.user.v1.UserService.CidSummary:input_type -> powergate.user.v1.CidSummaryRequest + 26, // 64: powergate.user.v1.UserService.CidInfo:input_type -> powergate.user.v1.CidInfoRequest + 28, // 65: powergate.user.v1.UserService.Balance:input_type -> powergate.user.v1.BalanceRequest + 30, // 66: powergate.user.v1.UserService.NewAddress:input_type -> powergate.user.v1.NewAddressRequest + 32, // 67: powergate.user.v1.UserService.Addresses:input_type -> powergate.user.v1.AddressesRequest + 34, // 68: powergate.user.v1.UserService.SendFil:input_type -> powergate.user.v1.SendFilRequest + 36, // 69: powergate.user.v1.UserService.SignMessage:input_type -> powergate.user.v1.SignMessageRequest + 38, // 70: powergate.user.v1.UserService.VerifyMessage:input_type -> powergate.user.v1.VerifyMessageRequest + 40, // 71: powergate.user.v1.UserService.StorageInfo:input_type -> powergate.user.v1.StorageInfoRequest + 42, // 72: powergate.user.v1.UserService.ListStorageInfo:input_type -> powergate.user.v1.ListStorageInfoRequest + 46, // 73: powergate.user.v1.UserService.StorageJob:input_type -> powergate.user.v1.StorageJobRequest + 48, // 74: powergate.user.v1.UserService.StorageConfigForJob:input_type -> powergate.user.v1.StorageConfigForJobRequest + 50, // 75: powergate.user.v1.UserService.ListStorageJobs:input_type -> powergate.user.v1.ListStorageJobsRequest + 52, // 76: powergate.user.v1.UserService.StorageJobsSummary:input_type -> powergate.user.v1.StorageJobsSummaryRequest + 54, // 77: powergate.user.v1.UserService.WatchStorageJobs:input_type -> powergate.user.v1.WatchStorageJobsRequest + 44, // 78: powergate.user.v1.UserService.CancelStorageJob:input_type -> powergate.user.v1.CancelStorageJobRequest + 56, // 79: powergate.user.v1.UserService.StorageDealRecords:input_type -> powergate.user.v1.StorageDealRecordsRequest + 58, // 80: powergate.user.v1.UserService.RetrievalDealRecords:input_type -> powergate.user.v1.RetrievalDealRecordsRequest + 3, // 81: powergate.user.v1.UserService.BuildInfo:output_type -> powergate.user.v1.BuildInfoResponse + 5, // 82: powergate.user.v1.UserService.UserIdentifier:output_type -> powergate.user.v1.UserIdentifierResponse + 7, // 83: powergate.user.v1.UserService.DefaultStorageConfig:output_type -> powergate.user.v1.DefaultStorageConfigResponse + 9, // 84: powergate.user.v1.UserService.SetDefaultStorageConfig:output_type -> powergate.user.v1.SetDefaultStorageConfigResponse + 15, // 85: powergate.user.v1.UserService.ApplyStorageConfig:output_type -> powergate.user.v1.ApplyStorageConfigResponse + 21, // 86: powergate.user.v1.UserService.Remove:output_type -> powergate.user.v1.RemoveResponse + 11, // 87: powergate.user.v1.UserService.Stage:output_type -> powergate.user.v1.StageResponse + 13, // 88: powergate.user.v1.UserService.StageCid:output_type -> powergate.user.v1.StageCidResponse + 17, // 89: powergate.user.v1.UserService.ReplaceData:output_type -> powergate.user.v1.ReplaceDataResponse + 19, // 90: powergate.user.v1.UserService.Get:output_type -> powergate.user.v1.GetResponse + 23, // 91: powergate.user.v1.UserService.WatchLogs:output_type -> powergate.user.v1.WatchLogsResponse + 25, // 92: powergate.user.v1.UserService.CidSummary:output_type -> powergate.user.v1.CidSummaryResponse + 27, // 93: powergate.user.v1.UserService.CidInfo:output_type -> powergate.user.v1.CidInfoResponse + 29, // 94: powergate.user.v1.UserService.Balance:output_type -> powergate.user.v1.BalanceResponse + 31, // 95: powergate.user.v1.UserService.NewAddress:output_type -> powergate.user.v1.NewAddressResponse + 33, // 96: powergate.user.v1.UserService.Addresses:output_type -> powergate.user.v1.AddressesResponse + 35, // 97: powergate.user.v1.UserService.SendFil:output_type -> powergate.user.v1.SendFilResponse + 37, // 98: powergate.user.v1.UserService.SignMessage:output_type -> powergate.user.v1.SignMessageResponse + 39, // 99: powergate.user.v1.UserService.VerifyMessage:output_type -> powergate.user.v1.VerifyMessageResponse + 41, // 100: powergate.user.v1.UserService.StorageInfo:output_type -> powergate.user.v1.StorageInfoResponse + 43, // 101: powergate.user.v1.UserService.ListStorageInfo:output_type -> powergate.user.v1.ListStorageInfoResponse + 47, // 102: powergate.user.v1.UserService.StorageJob:output_type -> powergate.user.v1.StorageJobResponse + 49, // 103: powergate.user.v1.UserService.StorageConfigForJob:output_type -> powergate.user.v1.StorageConfigForJobResponse + 51, // 104: powergate.user.v1.UserService.ListStorageJobs:output_type -> powergate.user.v1.ListStorageJobsResponse + 53, // 105: powergate.user.v1.UserService.StorageJobsSummary:output_type -> powergate.user.v1.StorageJobsSummaryResponse + 55, // 106: powergate.user.v1.UserService.WatchStorageJobs:output_type -> powergate.user.v1.WatchStorageJobsResponse + 45, // 107: powergate.user.v1.UserService.CancelStorageJob:output_type -> powergate.user.v1.CancelStorageJobResponse + 57, // 108: powergate.user.v1.UserService.StorageDealRecords:output_type -> powergate.user.v1.StorageDealRecordsResponse + 59, // 109: powergate.user.v1.UserService.RetrievalDealRecords:output_type -> powergate.user.v1.RetrievalDealRecordsResponse + 81, // [81:110] is the sub-list for method output_type + 52, // [52:81] is the sub-list for method input_type + 52, // [52:52] is the sub-list for extension type_name + 52, // [52:52] is the sub-list for extension extendee + 0, // [0:52] is the sub-list for field type_name } func init() { file_powergate_user_v1_user_proto_init() } @@ -6867,7 +7261,7 @@ func file_powergate_user_v1_user_proto_init() { } } file_powergate_user_v1_user_proto_msgTypes[65].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*StorageConfig); i { + switch v := v.(*WebhookAuthData); i { case 0: return &v.state case 1: @@ -6879,7 +7273,7 @@ func file_powergate_user_v1_user_proto_init() { } } file_powergate_user_v1_user_proto_msgTypes[66].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*IpfsHotInfo); i { + switch v := v.(*WebhookAuthentication); i { case 0: return &v.state case 1: @@ -6891,7 +7285,7 @@ func file_powergate_user_v1_user_proto_init() { } } file_powergate_user_v1_user_proto_msgTypes[67].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*HotInfo); i { + switch v := v.(*Webhook); i { case 0: return &v.state case 1: @@ -6903,7 +7297,7 @@ func file_powergate_user_v1_user_proto_init() { } } file_powergate_user_v1_user_proto_msgTypes[68].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*FilStorage); i { + switch v := v.(*WebhookAlert); i { case 0: return &v.state case 1: @@ -6915,7 +7309,7 @@ func file_powergate_user_v1_user_proto_init() { } } file_powergate_user_v1_user_proto_msgTypes[69].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*FilInfo); i { + switch v := v.(*WebhookConfiguration); i { case 0: return &v.state case 1: @@ -6927,7 +7321,7 @@ func file_powergate_user_v1_user_proto_init() { } } file_powergate_user_v1_user_proto_msgTypes[70].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ColdInfo); i { + switch v := v.(*NotificationConfig); i { case 0: return &v.state case 1: @@ -6939,7 +7333,7 @@ func file_powergate_user_v1_user_proto_init() { } } file_powergate_user_v1_user_proto_msgTypes[71].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*StorageInfo); i { + switch v := v.(*StorageConfig); i { case 0: return &v.state case 1: @@ -6951,7 +7345,7 @@ func file_powergate_user_v1_user_proto_init() { } } file_powergate_user_v1_user_proto_msgTypes[72].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CidInfo); i { + switch v := v.(*IpfsHotInfo); i { case 0: return &v.state case 1: @@ -6963,7 +7357,7 @@ func file_powergate_user_v1_user_proto_init() { } } file_powergate_user_v1_user_proto_msgTypes[73].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DealInfo); i { + switch v := v.(*HotInfo); i { case 0: return &v.state case 1: @@ -6975,7 +7369,7 @@ func file_powergate_user_v1_user_proto_init() { } } file_powergate_user_v1_user_proto_msgTypes[74].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*StorageJob); i { + switch v := v.(*FilStorage); i { case 0: return &v.state case 1: @@ -6987,7 +7381,7 @@ func file_powergate_user_v1_user_proto_init() { } } file_powergate_user_v1_user_proto_msgTypes[75].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DealError); i { + switch v := v.(*FilInfo); i { case 0: return &v.state case 1: @@ -6999,7 +7393,7 @@ func file_powergate_user_v1_user_proto_init() { } } file_powergate_user_v1_user_proto_msgTypes[76].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*LogEntry); i { + switch v := v.(*ColdInfo); i { case 0: return &v.state case 1: @@ -7011,7 +7405,7 @@ func file_powergate_user_v1_user_proto_init() { } } file_powergate_user_v1_user_proto_msgTypes[77].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DealRecordsConfig); i { + switch v := v.(*StorageInfo); i { case 0: return &v.state case 1: @@ -7023,7 +7417,7 @@ func file_powergate_user_v1_user_proto_init() { } } file_powergate_user_v1_user_proto_msgTypes[78].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*StorageDealInfo); i { + switch v := v.(*CidInfo); i { case 0: return &v.state case 1: @@ -7035,7 +7429,7 @@ func file_powergate_user_v1_user_proto_init() { } } file_powergate_user_v1_user_proto_msgTypes[79].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*StorageDealRecord); i { + switch v := v.(*DealInfo); i { case 0: return &v.state case 1: @@ -7047,7 +7441,7 @@ func file_powergate_user_v1_user_proto_init() { } } file_powergate_user_v1_user_proto_msgTypes[80].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*RetrievalDealInfo); i { + switch v := v.(*StorageJob); i { case 0: return &v.state case 1: @@ -7059,7 +7453,7 @@ func file_powergate_user_v1_user_proto_init() { } } file_powergate_user_v1_user_proto_msgTypes[81].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*RetrievalDealRecord); i { + switch v := v.(*DealError); i { case 0: return &v.state case 1: @@ -7071,6 +7465,78 @@ func file_powergate_user_v1_user_proto_init() { } } file_powergate_user_v1_user_proto_msgTypes[82].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*LogEntry); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_powergate_user_v1_user_proto_msgTypes[83].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DealRecordsConfig); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_powergate_user_v1_user_proto_msgTypes[84].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StorageDealInfo); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_powergate_user_v1_user_proto_msgTypes[85].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StorageDealRecord); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_powergate_user_v1_user_proto_msgTypes[86].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RetrievalDealInfo); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_powergate_user_v1_user_proto_msgTypes[87].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RetrievalDealRecord); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_powergate_user_v1_user_proto_msgTypes[88].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AddrInfo_VerifiedClientInfo); i { case 0: return &v.state @@ -7089,7 +7555,7 @@ func file_powergate_user_v1_user_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_powergate_user_v1_user_proto_rawDesc, NumEnums: 2, - NumMessages: 83, + NumMessages: 89, NumExtensions: 0, NumServices: 1, }, diff --git a/api/server/server.go b/api/server/server.go index 7c0dc1884..6334d272b 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -50,6 +50,7 @@ import ( "github.com/textileio/powergate/v2/iplocation/maxmind" "github.com/textileio/powergate/v2/lotus" "github.com/textileio/powergate/v2/migration" + "github.com/textileio/powergate/v2/notifications" "github.com/textileio/powergate/v2/reputation" txndstr "github.com/textileio/powergate/v2/txndstransform" "github.com/textileio/powergate/v2/util" @@ -253,8 +254,10 @@ func NewServer(conf Config) (*Server, error) { conf.DealWatchPollDuration = time.Second } + nt := notifications.New() + log.Info("Starting deals module...") - dm, err := dealsModule.New(txndstr.Wrap(ds, "deals"), clientBuilder, conf.DealWatchPollDuration, conf.FFSDealFinalityTimeout, deals.WithImportPath(filepath.Join(conf.RepoPath, "imports"))) + dm, err := dealsModule.New(txndstr.Wrap(ds, "deals"), nt, clientBuilder, conf.DealWatchPollDuration, conf.FFSDealFinalityTimeout, deals.WithImportPath(filepath.Join(conf.RepoPath, "imports"))) if err != nil { return nil, fmt.Errorf("creating deal module: %s", err) } diff --git a/api/server/user/storageconfig.go b/api/server/user/storageconfig.go index f8c9b4c49..44425d7f1 100644 --- a/api/server/user/storageconfig.go +++ b/api/server/user/storageconfig.go @@ -28,9 +28,10 @@ func (s *Service) SetDefaultStorageConfig(ctx context.Context, req *userPb.SetDe return nil, err } defaultConfig := ffs.StorageConfig{ - Repairable: req.Config.Repairable, - Hot: fromRPCHotConfig(req.Config.Hot), - Cold: fromRPCColdConfig(req.Config.Cold), + Repairable: req.Config.Repairable, + Hot: fromRPCHotConfig(req.Config.Hot), + Cold: fromRPCColdConfig(req.Config.Cold), + Notifications: fromRPCNotificationConfigs(req.Config.Notifications), } if err := i.SetDefaultStorageConfig(defaultConfig); err != nil { return nil, err @@ -54,9 +55,10 @@ func (s *Service) ApplyStorageConfig(ctx context.Context, req *userPb.ApplyStora if req.HasConfig { config := ffs.StorageConfig{ - Repairable: req.Config.Repairable, - Hot: fromRPCHotConfig(req.Config.Hot), - Cold: fromRPCColdConfig(req.Config.Cold), + Repairable: req.Config.Repairable, + Hot: fromRPCHotConfig(req.Config.Hot), + Cold: fromRPCColdConfig(req.Config.Cold), + Notifications: fromRPCNotificationConfigs(req.Config.Notifications), } options = append(options, api.WithStorageConfig(config)) } diff --git a/api/server/user/util.go b/api/server/user/util.go index 9defe3e2f..7cbfaab15 100644 --- a/api/server/user/util.go +++ b/api/server/user/util.go @@ -8,9 +8,10 @@ import ( func toRPCStorageConfig(config ffs.StorageConfig) *userPb.StorageConfig { return &userPb.StorageConfig{ - Repairable: config.Repairable, - Hot: toRPCHotConfig(config.Hot), - Cold: toRPCColdConfig(config.Cold), + Repairable: config.Repairable, + Hot: toRPCHotConfig(config.Hot), + Cold: toRPCColdConfig(config.Cold), + Notifications: toRPCNotificationConfigs(config.Notifications), } } @@ -47,6 +48,103 @@ func toRPCColdConfig(config ffs.ColdConfig) *userPb.ColdConfig { } } +func toRPCNotificationConfigs(notifications []*ffs.NotificationConfig) []*userPb.NotificationConfig { + if notifications == nil { + return nil + } + + var out []*userPb.NotificationConfig + + for _, cfg := range notifications { + if res := toRPCNotificationConfig(cfg); res != nil { + out = append(out, res) + } + } + + return out +} + +func toRPCNotificationConfig(cfg *ffs.NotificationConfig) *userPb.NotificationConfig { + if cfg == nil { + return nil + } + + return &userPb.NotificationConfig{ + Webhook: toRPCWebhook(cfg.Webhook), + Configuration: toRPCWebhookConfiguration(cfg.Configuration), + } +} + +func toRPCWebhook(webhook *ffs.Webhook) *userPb.Webhook { + if webhook == nil { + return nil + } + + return &userPb.Webhook{ + Endpoint: webhook.Endpoint, + Authentication: toRPCWebhookAuth(webhook.Authentication), + } +} + +func toRPCWebhookAuth(authentication *ffs.WebhookAuthentication) *userPb.WebhookAuthentication { + if authentication == nil { + return nil + } + + return &userPb.WebhookAuthentication{ + Type: authentication.Type, + Data: toRPCWebhookAuthData(authentication.Data), + } +} + +func toRPCWebhookAuthData(data *ffs.WebhookAuthData) *userPb.WebhookAuthData { + if data == nil { + return nil + } + + return &userPb.WebhookAuthData{ + Username: data.Username, + Password: data.Password, + } +} + +func toRPCWebhookConfiguration(configuration *ffs.WebhookConfiguration) *userPb.WebhookConfiguration { + if configuration == nil { + return nil + } + + return &userPb.WebhookConfiguration{ + Events: configuration.Events, + Alerts: toRPCWebhookAlerts(configuration.Alerts), + } +} + +func toRPCWebhookAlerts(alerts []*ffs.WebhookAlert) []*userPb.WebhookAlert { + if alerts == nil { + return nil + } + + var out []*userPb.WebhookAlert + for _, alert := range alerts { + if res := toRPCWebhookAlert(alert); res != nil { + out = append(out, res) + } + } + + return out +} + +func toRPCWebhookAlert(alert *ffs.WebhookAlert) *userPb.WebhookAlert { + if alert == nil { + return nil + } + + return &userPb.WebhookAlert{ + Type: alert.Type, + Threshold: alert.Threshold, + } +} + func fromRPCHotConfig(config *userPb.HotConfig) ffs.HotConfig { res := ffs.HotConfig{} if config != nil { @@ -93,6 +191,105 @@ func fromRPCColdConfig(config *userPb.ColdConfig) ffs.ColdConfig { return res } +func fromRPCNotificationConfigs(configs []*userPb.NotificationConfig) []*ffs.NotificationConfig { + if configs == nil { + return nil + } + + var out []*ffs.NotificationConfig + for _, cfg := range configs { + res := fromRPCNotificationConfig(cfg) + + if res != nil { + out = append(out, res) + } + } + + return out +} + +func fromRPCNotificationConfig(config *userPb.NotificationConfig) *ffs.NotificationConfig { + if config == nil { + return nil + } + + return &ffs.NotificationConfig{ + Webhook: fromRPCWebhook(config.Webhook), + Configuration: fromRPCWebhookConfiguration(config.Configuration), + } +} + +func fromRPCWebhookConfiguration(configuration *userPb.WebhookConfiguration) *ffs.WebhookConfiguration { + if configuration == nil { + return nil + } + + return &ffs.WebhookConfiguration{ + Events: configuration.Events, + Alerts: fromRPCWebhookAlerts(configuration.Alerts), + } +} + +func fromRPCWebhookAlerts(alerts []*userPb.WebhookAlert) []*ffs.WebhookAlert { + if alerts == nil { + return nil + } + + var out []*ffs.WebhookAlert + for _, alert := range alerts { + res := fromRPCWebhookAlert(alert) + if res != nil { + out = append(out, res) + } + } + + return out +} + +func fromRPCWebhookAlert(alert *userPb.WebhookAlert) *ffs.WebhookAlert { + if alert == nil { + return nil + } + + return &ffs.WebhookAlert{ + Type: alert.Type, + Threshold: alert.Threshold, + } +} + +func fromRPCWebhook(webhook *userPb.Webhook) *ffs.Webhook { + if webhook == nil { + return nil + } + + return &ffs.Webhook{ + Endpoint: webhook.Endpoint, + Authentication: fromRPCWebhookAuthentication(webhook.Authentication), + } +} + +func fromRPCWebhookAuthentication(authentication *userPb.WebhookAuthentication) *ffs.WebhookAuthentication { + if authentication == nil { + return nil + } + + return &ffs.WebhookAuthentication{ + Type: authentication.Type, + Data: fromRPCWebhookAuthenticationData(authentication.Data), + } +} + +func fromRPCWebhookAuthenticationData(data *userPb.WebhookAuthData) *ffs.WebhookAuthData { + if data == nil { + return nil + } + + return &ffs.WebhookAuthData{ + Username: data.Username, + Password: data.Password, + } +} + func buildListDealRecordsOptions(conf *userPb.DealRecordsConfig) []deals.DealRecordsOption { var opts []deals.DealRecordsOption if conf != nil { diff --git a/deals/module/module.go b/deals/module/module.go index a45cc2228..bf1d78cd9 100644 --- a/deals/module/module.go +++ b/deals/module/module.go @@ -19,6 +19,7 @@ import ( "github.com/textileio/powergate/v2/deals/module/dealwatcher" "github.com/textileio/powergate/v2/deals/module/store" "github.com/textileio/powergate/v2/lotus" + "github.com/textileio/powergate/v2/notifications" "go.opentelemetry.io/otel/metric" ) @@ -40,7 +41,7 @@ type Module struct { } // New creates a new Module. -func New(ds datastore.TxnDatastore, clientBuilder lotus.ClientBuilder, pollDuration time.Duration, dealFinalityTimeout time.Duration, opts ...deals.Option) (*Module, error) { +func New(ds datastore.TxnDatastore, nt notifications.Notifier, clientBuilder lotus.ClientBuilder, pollDuration time.Duration, dealFinalityTimeout time.Duration, opts ...deals.Option) (*Module, error) { var cfg deals.Config for _, o := range opts { if err := o(&cfg); err != nil { @@ -56,7 +57,7 @@ func New(ds datastore.TxnDatastore, clientBuilder lotus.ClientBuilder, pollDurat m := &Module{ clientBuilder: clientBuilder, cfg: &cfg, - store: store.New(ds), + store: store.New(ds, nt), pollDuration: pollDuration, dealFinalityTimeout: dealFinalityTimeout, dealWatcher: dw, diff --git a/deals/module/store/store.go b/deals/module/store/store.go index b0782039e..99f4932dd 100644 --- a/deals/module/store/store.go +++ b/deals/module/store/store.go @@ -17,6 +17,7 @@ import ( "github.com/ipfs/go-datastore/query" logging "github.com/ipfs/go-log/v2" "github.com/textileio/powergate/v2/deals" + "github.com/textileio/powergate/v2/notifications" "github.com/textileio/powergate/v2/util" "go.opentelemetry.io/otel/metric" ) @@ -39,6 +40,7 @@ var ( // Store stores deal and retrieval records. type Store struct { ds datastore.TxnDatastore + nt notifications.Notifier lock sync.Mutex metricFinalTotal metric.Int64Counter @@ -46,9 +48,10 @@ type Store struct { } // New returns a new *Store. -func New(ds datastore.TxnDatastore) *Store { +func New(ds datastore.TxnDatastore, nt notifications.Notifier) *Store { s := &Store{ ds: ds, + nt: nt, } s.initMetrics() @@ -112,6 +115,8 @@ func (s *Store) PutStorageDeal(dr deals.StorageDealRecord) error { return fmt.Errorf("committing transaction: %s", err) } + s.nt.NotifyDeal(dr) + return nil } diff --git a/ffs/integrationtest/manager/manager.go b/ffs/integrationtest/manager/manager.go index 906dfb8ae..a94c40a16 100644 --- a/ffs/integrationtest/manager/manager.go +++ b/ffs/integrationtest/manager/manager.go @@ -20,6 +20,7 @@ import ( "github.com/textileio/powergate/v2/ffs/scheduler" "github.com/textileio/powergate/v2/filchain" "github.com/textileio/powergate/v2/lotus" + "github.com/textileio/powergate/v2/notifications" "github.com/textileio/powergate/v2/tests" "github.com/textileio/powergate/v2/util" @@ -79,7 +80,8 @@ func NewFFSManager(t require.TestingT, ds datastore.TxnDatastore, clientBuilder // NewCustomFFSManager returns a new customized FFS manager. func NewCustomFFSManager(t require.TestingT, ds datastore.TxnDatastore, cb lotus.ClientBuilder, masterAddr address.Address, ms ffs.MinerSelector, ipfsClient *httpapi.HttpApi, minimumPieceSize uint64) (*manager.Manager, *coreipfs.CoreIpfs, func()) { - dm, err := dealsModule.New(txndstr.Wrap(ds, "deals"), cb, util.AvgBlockTime, time.Minute*10) + nt := notifications.New() + dm, err := dealsModule.New(txndstr.Wrap(ds, "deals"), nt, cb, util.AvgBlockTime, time.Minute*10) require.NoError(t, err) fchain := filchain.New(cb) diff --git a/ffs/types.go b/ffs/types.go index 50bb8836b..8341eb29c 100644 --- a/ffs/types.go +++ b/ffs/types.go @@ -132,9 +132,10 @@ type RetrievalJob struct { // StorageConfig contains a default storage configuration for an Api instance. type StorageConfig struct { - Hot HotConfig - Cold ColdConfig - Repairable bool + Hot HotConfig + Cold ColdConfig + Repairable bool + Notifications []*NotificationConfig } // WithRepairable allows to enable/disable auto-repair. @@ -342,6 +343,36 @@ func (cc ColdConfig) Validate() error { return nil } +type NotificationConfig struct { + Webhook *Webhook + Configuration *WebhookConfiguration +} + +type Webhook struct { + Endpoint string + Authentication *WebhookAuthentication +} + +type WebhookAuthentication struct { + Type string + Data *WebhookAuthData +} + +type WebhookAuthData struct { + Username string + Password string +} + +type WebhookConfiguration struct { + Events []string + Alerts []*WebhookAlert +} + +type WebhookAlert struct { + Type string + Threshold string +} + // FilConfig is the desired state of a Cid in the Filecoin network. type FilConfig struct { // RepFactor indicates the desired amount of active deals diff --git a/notifications/notifier.go b/notifications/notifier.go new file mode 100644 index 000000000..a772f8597 --- /dev/null +++ b/notifications/notifier.go @@ -0,0 +1,31 @@ +package notifications + +import ( + "bytes" + "encoding/json" + "net/http" + + "github.com/textileio/powergate/v2/deals" +) + +type Notifier interface { + NotifyDeal(dr deals.StorageDealRecord) +} + +type notifier struct { + client *http.Client +} + +func New() *notifier { + return ¬ifier{ + client: http.DefaultClient, + } +} + +func (n *notifier) NotifyDeal(dr deals.StorageDealRecord) { + endpoint := "https://vmanilo.free.beeceptor.com/webhook" + contentType := "application/json" + payload, _ := json.Marshal(dr) + + n.client.Post(endpoint, contentType, bytes.NewBuffer(payload)) +} \ No newline at end of file diff --git a/proto/powergate/user/v1/user.proto b/proto/powergate/user/v1/user.proto index f5ce4eaae..1d47881c0 100644 --- a/proto/powergate/user/v1/user.proto +++ b/proto/powergate/user/v1/user.proto @@ -371,10 +371,41 @@ message ColdConfig { FilConfig filecoin = 2; } +message WebhookAuthData { + string username = 1; + string password = 2; +} + +message WebhookAuthentication { + string type = 1; + WebhookAuthData data = 2; +} + +message Webhook { + string endpoint = 1; + WebhookAuthentication authentication = 2; +} + +message WebhookAlert { + string type = 1; + string threshold = 2; +} + +message WebhookConfiguration { + repeated string events = 1; + repeated WebhookAlert alerts = 2; +} + +message NotificationConfig { + Webhook webhook = 1; + WebhookConfiguration configuration = 2; +} + message StorageConfig { HotConfig hot = 1; ColdConfig cold = 2; bool repairable = 3; + repeated NotificationConfig notifications = 4; } message IpfsHotInfo { From b24f0c20013493f9a4baf0ce0bb818e01b1b9a0b Mon Sep 17 00:00:00 2001 From: Volodymyr Manilo Date: Wed, 8 Jun 2022 18:53:46 +0200 Subject: [PATCH 2/7] added notification for storage jobs --- api/server/server.go | 6 +- deals/module/module.go | 5 +- deals/module/store/store.go | 7 +- docker/docker-compose-localnet.yaml | 3 +- docker/docker-compose.yaml | 3 +- ffs/integrationtest/manager/manager.go | 6 +- ffs/manager/manager.go | 12 ++ ffs/scheduler/internal/sjstore/sjstore.go | 11 +- .../internal/sjstore/sjstore_test.go | 2 +- ffs/scheduler/scheduler.go | 29 ++-- ffs/scheduler/scheduler_storage.go | 3 + ffs/types.go | 33 +++++ notifications/notifier.go | 140 ++++++++++++++++-- tests/txmapds.go | 13 ++ 14 files changed, 233 insertions(+), 40 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index 6334d272b..183135c5f 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -254,10 +254,10 @@ func NewServer(conf Config) (*Server, error) { conf.DealWatchPollDuration = time.Second } - nt := notifications.New() + notifier := notifications.New() log.Info("Starting deals module...") - dm, err := dealsModule.New(txndstr.Wrap(ds, "deals"), nt, clientBuilder, conf.DealWatchPollDuration, conf.FFSDealFinalityTimeout, deals.WithImportPath(filepath.Join(conf.RepoPath, "imports"))) + dm, err := dealsModule.New(txndstr.Wrap(ds, "deals"), clientBuilder, conf.DealWatchPollDuration, conf.FFSDealFinalityTimeout, deals.WithImportPath(filepath.Join(conf.RepoPath, "imports"))) if err != nil { return nil, fmt.Errorf("creating deal module: %s", err) } @@ -297,7 +297,7 @@ func NewServer(conf Config) (*Server, error) { sr2rf = ms.GetReplicationFactor } gcConfig := scheduler.GCConfig{StageGracePeriod: conf.FFSGCStageGracePeriod, AutoGCInterval: conf.FFSGCAutomaticGCInterval} - sched, err := scheduler.New(txndstr.Wrap(ds, "ffs/scheduler"), l, hs, cs, conf.SchedMaxParallel, conf.FFSDealFinalityTimeout, sr2rf, gcConfig) + sched, err := scheduler.New(txndstr.Wrap(ds, "ffs/scheduler"), l, hs, cs, conf.SchedMaxParallel, conf.FFSDealFinalityTimeout, sr2rf, gcConfig, notifier) if err != nil { return nil, fmt.Errorf("creating scheduler: %s", err) } diff --git a/deals/module/module.go b/deals/module/module.go index bf1d78cd9..a45cc2228 100644 --- a/deals/module/module.go +++ b/deals/module/module.go @@ -19,7 +19,6 @@ import ( "github.com/textileio/powergate/v2/deals/module/dealwatcher" "github.com/textileio/powergate/v2/deals/module/store" "github.com/textileio/powergate/v2/lotus" - "github.com/textileio/powergate/v2/notifications" "go.opentelemetry.io/otel/metric" ) @@ -41,7 +40,7 @@ type Module struct { } // New creates a new Module. -func New(ds datastore.TxnDatastore, nt notifications.Notifier, clientBuilder lotus.ClientBuilder, pollDuration time.Duration, dealFinalityTimeout time.Duration, opts ...deals.Option) (*Module, error) { +func New(ds datastore.TxnDatastore, clientBuilder lotus.ClientBuilder, pollDuration time.Duration, dealFinalityTimeout time.Duration, opts ...deals.Option) (*Module, error) { var cfg deals.Config for _, o := range opts { if err := o(&cfg); err != nil { @@ -57,7 +56,7 @@ func New(ds datastore.TxnDatastore, nt notifications.Notifier, clientBuilder lot m := &Module{ clientBuilder: clientBuilder, cfg: &cfg, - store: store.New(ds, nt), + store: store.New(ds), pollDuration: pollDuration, dealFinalityTimeout: dealFinalityTimeout, dealWatcher: dw, diff --git a/deals/module/store/store.go b/deals/module/store/store.go index 99f4932dd..b0782039e 100644 --- a/deals/module/store/store.go +++ b/deals/module/store/store.go @@ -17,7 +17,6 @@ import ( "github.com/ipfs/go-datastore/query" logging "github.com/ipfs/go-log/v2" "github.com/textileio/powergate/v2/deals" - "github.com/textileio/powergate/v2/notifications" "github.com/textileio/powergate/v2/util" "go.opentelemetry.io/otel/metric" ) @@ -40,7 +39,6 @@ var ( // Store stores deal and retrieval records. type Store struct { ds datastore.TxnDatastore - nt notifications.Notifier lock sync.Mutex metricFinalTotal metric.Int64Counter @@ -48,10 +46,9 @@ type Store struct { } // New returns a new *Store. -func New(ds datastore.TxnDatastore, nt notifications.Notifier) *Store { +func New(ds datastore.TxnDatastore) *Store { s := &Store{ ds: ds, - nt: nt, } s.initMetrics() @@ -115,8 +112,6 @@ func (s *Store) PutStorageDeal(dr deals.StorageDealRecord) error { return fmt.Errorf("committing transaction: %s", err) } - s.nt.NotifyDeal(dr) - return nil } diff --git a/docker/docker-compose-localnet.yaml b/docker/docker-compose-localnet.yaml index 8c466f5d0..efcea8c00 100644 --- a/docker/docker-compose-localnet.yaml +++ b/docker/docker-compose-localnet.yaml @@ -8,7 +8,7 @@ services: - 6060:6060 - 5002:5002 - 6002:6002 - - 7000:7000 + - 7001:7001 depends_on: - ipfs - lotus @@ -16,6 +16,7 @@ services: - POWD_DEVNET=true - POWD_LOTUSHOST=/dns4/lotus/tcp/7777 - POWD_IPFSAPIADDR=/dns4/ipfs/tcp/5001 + - POW_GATEWAYHOSTADDR=0.0.0.0:7001 restart: unless-stopped ipfs: diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml index 25a3d985f..02d16ff7a 100644 --- a/docker/docker-compose.yaml +++ b/docker/docker-compose.yaml @@ -14,7 +14,7 @@ services: - 6060:6060 - 5002:5002 - 6002:6002 - - 7000:7000 + - 7001:7001 depends_on: - ipfs - lotus @@ -23,6 +23,7 @@ services: - POWD_IPFSAPIADDR=/dns4/ipfs/tcp/5001 - POWD_LOTUSTOKENFILE=/root/lotus/.lotus/token - POWD_REPOPATH=/root/powergate/.powergate + - POW_GATEWAYHOSTADDR=0.0.0.0:7001 restart: unless-stopped volumes: - powergate-powd:/root/powergate diff --git a/ffs/integrationtest/manager/manager.go b/ffs/integrationtest/manager/manager.go index a94c40a16..7871b5d66 100644 --- a/ffs/integrationtest/manager/manager.go +++ b/ffs/integrationtest/manager/manager.go @@ -80,8 +80,8 @@ func NewFFSManager(t require.TestingT, ds datastore.TxnDatastore, clientBuilder // NewCustomFFSManager returns a new customized FFS manager. func NewCustomFFSManager(t require.TestingT, ds datastore.TxnDatastore, cb lotus.ClientBuilder, masterAddr address.Address, ms ffs.MinerSelector, ipfsClient *httpapi.HttpApi, minimumPieceSize uint64) (*manager.Manager, *coreipfs.CoreIpfs, func()) { - nt := notifications.New() - dm, err := dealsModule.New(txndstr.Wrap(ds, "deals"), nt, cb, util.AvgBlockTime, time.Minute*10) + notifier := notifications.New() + dm, err := dealsModule.New(txndstr.Wrap(ds, "deals"), cb, util.AvgBlockTime, time.Minute*10) require.NoError(t, err) fchain := filchain.New(cb) @@ -91,7 +91,7 @@ func NewCustomFFSManager(t require.TestingT, ds datastore.TxnDatastore, cb lotus cl := filcold.New(ms, dm, nil, ipfsClient, fchain, l, lsm, minimumPieceSize, 1, time.Hour) hl, err := coreipfs.New(ds, ipfsClient, l) require.NoError(t, err) - sched, err := scheduler.New(txndstr.Wrap(ds, "ffs/scheduler"), l, hl, cl, 10, time.Minute*10, nil, scheduler.GCConfig{AutoGCInterval: 0}) + sched, err := scheduler.New(txndstr.Wrap(ds, "ffs/scheduler"), l, hl, cl, 10, time.Minute*10, nil, scheduler.GCConfig{AutoGCInterval: 0}, notifier) require.NoError(t, err) wm, err := lotusWallet.New(cb, masterAddr, *big.NewInt(iWalletBal), false, "") diff --git a/ffs/manager/manager.go b/ffs/manager/manager.go index f17ee36cf..0a2712b7c 100644 --- a/ffs/manager/manager.go +++ b/ffs/manager/manager.go @@ -63,6 +63,18 @@ var ( DealMinDuration: util.MinDealDuration, }, }, + + // TODO: remove after review, just for testing + Notifications: []*ffs.NotificationConfig{ + { + Webhook: &ffs.Webhook{ + Endpoint: "https://vmanilo.free.beeceptor.com/webhook/job", + }, + Configuration: &ffs.WebhookConfiguration{ + Events: []string{"storage-deal-created"}, + }, + }, + }, } dsDefaultStorageConfigKey = datastore.NewKey("defaultstorageconfig") ) diff --git a/ffs/scheduler/internal/sjstore/sjstore.go b/ffs/scheduler/internal/sjstore/sjstore.go index fd97cdb61..3616f58b9 100644 --- a/ffs/scheduler/internal/sjstore/sjstore.go +++ b/ffs/scheduler/internal/sjstore/sjstore.go @@ -16,6 +16,7 @@ import ( logging "github.com/ipfs/go-log/v2" "github.com/textileio/powergate/v2/deals" "github.com/textileio/powergate/v2/ffs" + "github.com/textileio/powergate/v2/notifications" "github.com/textileio/powergate/v2/util" "go.opentelemetry.io/otel/metric" ) @@ -64,6 +65,8 @@ type Store struct { // Metrics metricJobCounter metric.Int64UpDownCounter + + notifier notifications.Notifier } // Stats return metrics about current job queues. @@ -78,7 +81,7 @@ type watcher struct { } // New returns a new JobStore backed by the Datastore. -func New(ds datastore.TxnDatastore) (*Store, error) { +func New(ds datastore.TxnDatastore, notifier notifications.Notifier) (*Store, error) { s := &Store{ ds: ds, jobStatusCache: make(map[ffs.APIID]map[cid.Cid]map[cid.Cid]deals.StorageDealInfo), @@ -88,6 +91,7 @@ func New(ds datastore.TxnDatastore) (*Store, error) { lastSuccessfulJobs: make(map[ffs.APIID]map[cid.Cid]*ffs.StorageJob), queuedIDs: make(map[ffs.JobID]struct{}), executingIDs: make(map[ffs.JobID]struct{}), + notifier: notifier, } s.initMetrics() if err := s.loadCaches(); err != nil { @@ -116,8 +120,13 @@ func (s *Store) MonitorJob(j ffs.StorageJob) chan deals.StorageDealInfo { if err != nil { log.Errorf("getting job: %v", err) s.lock.Unlock() + // do we need to continue here? + // probably we will fail to get job from the store on the next iteration as well continue } + + s.notifier.NotifyStorageJob(job, update) + values := make([]deals.StorageDealInfo, 0, len(s.jobStatusCache[j.APIID][j.Cid])) for _, v := range s.jobStatusCache[j.APIID][j.Cid] { values = append(values, v) diff --git a/ffs/scheduler/internal/sjstore/sjstore_test.go b/ffs/scheduler/internal/sjstore/sjstore_test.go index caea44c28..c8222ede1 100644 --- a/ffs/scheduler/internal/sjstore/sjstore_test.go +++ b/ffs/scheduler/internal/sjstore/sjstore_test.go @@ -512,7 +512,7 @@ func createJob(_ *testing.T, apiid string, c cid.Cid) ffs.StorageJob { func create(t *testing.T) *Store { ds := tests.NewTxMapDatastore() - store, err := New(ds) + store, err := New(ds, tests.NewMockNotifier()) require.NoError(t, err) return store } diff --git a/ffs/scheduler/scheduler.go b/ffs/scheduler/scheduler.go index bc53c5a9f..d70f1e425 100644 --- a/ffs/scheduler/scheduler.go +++ b/ffs/scheduler/scheduler.go @@ -18,6 +18,7 @@ import ( "github.com/textileio/powergate/v2/ffs/scheduler/internal/rjstore" "github.com/textileio/powergate/v2/ffs/scheduler/internal/sjstore" "github.com/textileio/powergate/v2/ffs/scheduler/internal/trackstore" + "github.com/textileio/powergate/v2/notifications" txndstr "github.com/textileio/powergate/v2/txndstransform" ) @@ -41,15 +42,16 @@ var ( // This Jobs are executed by delegating the work to the hot and cold storage configured for // the scheduler. type Scheduler struct { - cs ffs.ColdStorage - hs ffs.HotStorage - sjs *sjstore.Store - rjs *rjstore.Store - as *astore.Store - ts *trackstore.Store - cis *cistore.Store - ris *ristore.Store - l ffs.JobLogger + cs ffs.ColdStorage + hs ffs.HotStorage + sjs *sjstore.Store + rjs *rjstore.Store + as *astore.Store + ts *trackstore.Store + cis *cistore.Store + ris *ristore.Store + l ffs.JobLogger + notifier notifications.Notifier sr2RepFactor func() (int, error) dealFinalityTimeout time.Duration @@ -89,8 +91,8 @@ type GCConfig struct { // New returns a new instance of Scheduler which uses JobStore as its backing repository for state, // HotStorage for the hot layer, and ColdStorage for the cold layer. -func New(ds datastore.TxnDatastore, l ffs.JobLogger, hs ffs.HotStorage, cs ffs.ColdStorage, maxParallel int, dealFinalityTimeout time.Duration, sr2rf func() (int, error), gcConfig GCConfig) (*Scheduler, error) { - sjs, err := sjstore.New(txndstr.Wrap(ds, "sjstore")) +func New(ds datastore.TxnDatastore, l ffs.JobLogger, hs ffs.HotStorage, cs ffs.ColdStorage, maxParallel int, dealFinalityTimeout time.Duration, sr2rf func() (int, error), gcConfig GCConfig, notifier notifications.Notifier) (*Scheduler, error) { + sjs, err := sjstore.New(txndstr.Wrap(ds, "sjstore"), notifier) if err != nil { return nil, fmt.Errorf("loading stroage jobstore: %s", err) } @@ -124,6 +126,8 @@ func New(ds datastore.TxnDatastore, l ffs.JobLogger, hs ffs.HotStorage, cs ffs.C l: l, gc: gcConfig, + notifier: notifier, + jobsCancel: make(map[ffs.JobID]chan struct{}), sd: storageDaemon{ rateLim: make(chan struct{}, maxParallel), @@ -520,9 +524,12 @@ func (s *Scheduler) executeQueuedStorage(j ffs.StorageJob) { return } + s.notifier.RegisterStorageJob(j, a.Cfg.Notifications) + // Execute s.l.Log(ctx, "Executing job %s...", j.ID) dealUpdates := s.sjs.MonitorJob(j) + // TODO: vova: job executes start here info, dealErrors, err := s.executeStorage(ctx, a, j, dealUpdates) close(dealUpdates) // Something bad-enough happened to make Job diff --git a/ffs/scheduler/scheduler_storage.go b/ffs/scheduler/scheduler_storage.go index c00fdf317..f5b626cf5 100644 --- a/ffs/scheduler/scheduler_storage.go +++ b/ffs/scheduler/scheduler_storage.go @@ -81,6 +81,9 @@ func (s *Scheduler) push(iid ffs.APIID, c cid.Cid, cfg ffs.StorageConfig, oldCid } s.l.Log(ctx, "Configuration saved successfully") + + s.notifier.RegisterStorageJob(j, cfg.Notifications) + return jid, nil } diff --git a/ffs/types.go b/ffs/types.go index 8341eb29c..f5e91fe3f 100644 --- a/ffs/types.go +++ b/ffs/types.go @@ -2,7 +2,10 @@ package ffs import ( "context" + "encoding/base64" "fmt" + "io" + "net/http" "time" "github.com/google/uuid" @@ -353,16 +356,46 @@ type Webhook struct { Authentication *WebhookAuthentication } +func (w *Webhook) Publish(client *http.Client, payload io.Reader) error { + req, err := http.NewRequest("POST", w.Endpoint, payload) + if err != nil { + return err + } + req.Header.Set("Content-Type", "application/json") + + if w.Authentication != nil { + w.Authentication.setHeaders(req) + } + + _, err = client.Do(req) + return err +} + type WebhookAuthentication struct { Type string Data *WebhookAuthData } +func (a *WebhookAuthentication) setHeaders(req *http.Request) { + switch a.Type { + case "basic_auth": + if a.Data != nil { + req.Header.Set("Authorization", fmt.Sprintf("Basic %s", a.Data.encode())) + } + } +} + type WebhookAuthData struct { Username string Password string } +func (d WebhookAuthData) encode() string { + return base64.StdEncoding.EncodeToString( + []byte(fmt.Sprintf("%s:%s", d.Username, d.Password)), + ) +} + type WebhookConfiguration struct { Events []string Alerts []*WebhookAlert diff --git a/notifications/notifier.go b/notifications/notifier.go index a772f8597..5c767b215 100644 --- a/notifications/notifier.go +++ b/notifications/notifier.go @@ -4,28 +4,148 @@ import ( "bytes" "encoding/json" "net/http" + "sync" "github.com/textileio/powergate/v2/deals" + "github.com/textileio/powergate/v2/ffs" ) type Notifier interface { - NotifyDeal(dr deals.StorageDealRecord) + RegisterStorageJob(job ffs.StorageJob, notificationConfig []*ffs.NotificationConfig) + NotifyStorageJob(job ffs.StorageJob, dealInfo deals.StorageDealInfo) +} + +type storageJobUpdate struct { + job ffs.StorageJob + dealInfo deals.StorageDealInfo } type notifier struct { - client *http.Client + client *http.Client + store *configStore + storageJobNotifications chan *storageJobUpdate } func New() *notifier { - return ¬ifier{ - client: http.DefaultClient, + nt := ¬ifier{ + client: http.DefaultClient, + store: newConfigStore(), + storageJobNotifications: make(chan *storageJobUpdate, 1000), + } + + go nt.run() + + return nt +} + +func (n *notifier) RegisterStorageJob(job ffs.StorageJob, notificationConfig []*ffs.NotificationConfig) { + if notificationConfig == nil { + return + } + + n.store.put(job, notificationConfig) +} + +func (n *notifier) NotifyStorageJob(job ffs.StorageJob, dealInfo deals.StorageDealInfo) { + n.storageJobNotifications <- &storageJobUpdate{ + job: job, + dealInfo: dealInfo, + } +} + +func (n *notifier) run() { + for updates := range n.storageJobNotifications { + if updates == nil { + continue + } + + config := n.store.get(updates.job.ID) + if config == nil { + continue + } + + n.notifyAll(config.notifications, updates) + } +} + +func (n *notifier) notifyAll(configs []*ffs.NotificationConfig, updates *storageJobUpdate) { + for _, cfg := range configs { + n.notify(cfg, updates) + } +} + +func (n *notifier) notify(config *ffs.NotificationConfig, updates *storageJobUpdate) { + if matchNotificationConfig(config.Configuration, updates.dealInfo) { + n.publishNotification(config.Webhook, updates) + } +} + +func matchNotificationConfig(config *ffs.WebhookConfiguration, updates deals.StorageDealInfo) bool { + if config == nil { + return false } + + return matchNotificationEvents(config.Events, updates) || matchNotificationAlerts(config.Alerts, updates) +} + +func matchNotificationEvents(events []string, updates deals.StorageDealInfo) bool { + // TODO + return true } -func (n *notifier) NotifyDeal(dr deals.StorageDealRecord) { - endpoint := "https://vmanilo.free.beeceptor.com/webhook" - contentType := "application/json" - payload, _ := json.Marshal(dr) +func matchNotificationAlerts(alerts []*ffs.WebhookAlert, updates deals.StorageDealInfo) bool { + // TODO + return false +} - n.client.Post(endpoint, contentType, bytes.NewBuffer(payload)) -} \ No newline at end of file +func (n *notifier) publishNotification(webhook *ffs.Webhook, updates *storageJobUpdate) { + if webhook == nil { + return + } + + data, err := json.Marshal(updates.dealInfo) + if err != nil { + // TODO: log error + return + } + + err = webhook.Publish(n.client, bytes.NewBuffer(data)) + if err != nil { + // TODO: log error + return + } +} + +type configStore struct { + sync.RWMutex + + configs map[ffs.JobID]*jobConfig +} + +func newConfigStore() *configStore { + return &configStore{ + configs: make(map[ffs.JobID]*jobConfig), + } +} + +type jobConfig struct { + job ffs.StorageJob + notifications []*ffs.NotificationConfig +} + +func (s *configStore) put(job ffs.StorageJob, notifications []*ffs.NotificationConfig) { + s.Lock() + defer s.Unlock() + + s.configs[job.ID] = &jobConfig{ + job: job, + notifications: notifications, + } +} + +func (s *configStore) get(jobID ffs.JobID) *jobConfig { + s.RLock() + defer s.RUnlock() + + return s.configs[jobID] +} diff --git a/tests/txmapds.go b/tests/txmapds.go index 736700477..95bd7b29d 100644 --- a/tests/txmapds.go +++ b/tests/txmapds.go @@ -6,6 +6,8 @@ import ( "github.com/ipfs/go-datastore" "github.com/ipfs/go-datastore/query" + "github.com/textileio/powergate/v2/deals" + "github.com/textileio/powergate/v2/ffs" ) // TxMapDatastore is a in-memory datastore that satisfies TxnDatastore. @@ -172,3 +174,14 @@ func (bt *SimpleTx) Commit() error { return err } + +type mockNotifier struct{} + +func NewMockNotifier() *mockNotifier { + return &mockNotifier{} +} + +func (n *mockNotifier) NotifyDealRecord(dr deals.StorageDealRecord) {} +func (n *mockNotifier) RegisterStorageJob(job ffs.StorageJob, notificationConfig []*ffs.NotificationConfig) { +} +func (n *mockNotifier) NotifyStorageJob(jobID ffs.JobID, dealInfo deals.StorageDealInfo) {} From b45698bfa01cd1efd979ea4bf15ffe5fcec773fe Mon Sep 17 00:00:00 2001 From: Volodymyr Manilo Date: Tue, 14 Jun 2022 22:29:30 +0300 Subject: [PATCH 3/7] added notification payload --- ffs/scheduler/scheduler.go | 1 - notifications/notifier.go | 120 ++++++++++++++++++++++++++++++++++++- 2 files changed, 117 insertions(+), 4 deletions(-) diff --git a/ffs/scheduler/scheduler.go b/ffs/scheduler/scheduler.go index d70f1e425..594c2395d 100644 --- a/ffs/scheduler/scheduler.go +++ b/ffs/scheduler/scheduler.go @@ -529,7 +529,6 @@ func (s *Scheduler) executeQueuedStorage(j ffs.StorageJob) { // Execute s.l.Log(ctx, "Executing job %s...", j.ID) dealUpdates := s.sjs.MonitorJob(j) - // TODO: vova: job executes start here info, dealErrors, err := s.executeStorage(ctx, a, j, dealUpdates) close(dealUpdates) // Something bad-enough happened to make Job diff --git a/notifications/notifier.go b/notifications/notifier.go index 5c767b215..a5413a96a 100644 --- a/notifications/notifier.go +++ b/notifications/notifier.go @@ -6,6 +6,7 @@ import ( "net/http" "sync" + "github.com/filecoin-project/go-fil-markets/storagemarket" "github.com/textileio/powergate/v2/deals" "github.com/textileio/powergate/v2/ffs" ) @@ -75,6 +76,10 @@ func (n *notifier) notifyAll(configs []*ffs.NotificationConfig, updates *storage } func (n *notifier) notify(config *ffs.NotificationConfig, updates *storageJobUpdate) { + if config == nil { + return + } + if matchNotificationConfig(config.Configuration, updates.dealInfo) { n.publishNotification(config.Webhook, updates) } @@ -88,9 +93,92 @@ func matchNotificationConfig(config *ffs.WebhookConfiguration, updates deals.Sto return matchNotificationEvents(config.Events, updates) || matchNotificationAlerts(config.Alerts, updates) } +const ( + all = "*" + created = "created" + completed = "completed" + retried = "retried" + failed = "failed" + expired = "expired" + slashed = "slashed" + separator = "-" + storageDeal = "storage-deal" + storageAuction = "storage-auction" + dataRetrieval = "data-retrieval" + + AllEvents = all + AllCreatedEvents = all + separator + created + AllCompletedEvents = all + separator + completed + AllRetriedEvents = all + separator + retried + AllFailedEvents = all + separator + failed + + AllStorageDealEvents = storageDeal + separator + all + StorageDealCreatedEvent = storageDeal + separator + created + StorageDealCompletedEvent = storageDeal + separator + completed + StorageDealRetriedEvent = storageDeal + separator + retried + StorageDealFailedEvent = storageDeal + separator + failed + StorageDealExpiredEvent = storageDeal + separator + expired + StorageDealSlashedEvent = storageDeal + separator + slashed + + AllStorageAuctionEvents = storageAuction + separator + all + StorageAuctionCreatedEvent = storageAuction + separator + created + StorageAuctionCompletedEvent = storageAuction + separator + completed + StorageAuctionFailedEvent = storageAuction + separator + failed + + AllDataRetrievalEvents = dataRetrieval + separator + all + DataRetrievalCompletedEvent = dataRetrieval + separator + completed + DataRetrievalRetriedEvent = dataRetrieval + separator + retried + DataRetrievalFailedEvent = dataRetrieval + separator + failed +) + func matchNotificationEvents(events []string, updates deals.StorageDealInfo) bool { - // TODO - return true + for _, event := range events { + if matchNotificationEvent(event, updates) { + return true + } + } + + return false +} + +func matchNotificationEvent(event string, updates deals.StorageDealInfo) bool { + switch event { + case AllEvents: + return true + case AllCreatedEvents: + // TODO: add created events + return updates.DealID != 0 // || + case AllCompletedEvents: + // TODO: add other completed events + return updates.StateID == storagemarket.StorageDealActive // || + + // TODO: + // case AllRetriedEvents: + + case AllStorageDealEvents: + return true + + case StorageDealCreatedEvent: + return updates.DealID != 0 + + case StorageDealCompletedEvent: + return updates.StateID == storagemarket.StorageDealActive + + // TODO: + // case StorageDealRetriedEvent: + + case StorageDealFailedEvent: + return updates.StateID == storagemarket.StorageDealFailing || updates.StateID == storagemarket.StorageDealError || updates.Message != "" + + case StorageDealExpiredEvent: + return updates.StateID == storagemarket.StorageDealExpired + + case StorageDealSlashedEvent: + return updates.StateID == storagemarket.StorageDealSlashed + + default: + return false + } } func matchNotificationAlerts(alerts []*ffs.WebhookAlert, updates deals.StorageDealInfo) bool { @@ -98,12 +186,38 @@ func matchNotificationAlerts(alerts []*ffs.WebhookAlert, updates deals.StorageDe return false } +type notification struct { + Cid string `json:"cid"` + JobID ffs.JobID `json:"jobId"` + JobStatus string `json:"jobStatus"` + Miner string `json:"miner"` + Price uint64 `json:"price"` + ProposalCid string `json:"proposalCid"` + DealID uint64 `json:"dealId,omitempty"` + DealStatus string `json:"dealStatus"` + ErrCause string `json:"error,omitempty"` + Message string `json:"message,omitempty"` +} + func (n *notifier) publishNotification(webhook *ffs.Webhook, updates *storageJobUpdate) { if webhook == nil { return } - data, err := json.Marshal(updates.dealInfo) + obj := ¬ification{ + Cid: updates.job.Cid.String(), + JobID: updates.job.ID, + JobStatus: ffs.JobStatusStr[updates.job.Status], + Miner: updates.dealInfo.Miner, + Price: updates.dealInfo.PricePerEpoch, + ProposalCid: updates.dealInfo.ProposalCid.String(), + DealID: updates.dealInfo.DealID, + DealStatus: updates.dealInfo.StateName, + ErrCause: updates.job.ErrCause, + Message: updates.dealInfo.Message, + } + + data, err := json.Marshal(obj) if err != nil { // TODO: log error return From 6e2a5471635df43bea789f3e7ff0459459c2a478 Mon Sep 17 00:00:00 2001 From: Volodymyr Manilo Date: Tue, 21 Jun 2022 16:23:48 +0300 Subject: [PATCH 4/7] refactor notifier --- api/server/server.go | 5 +- ffs/api/api_retrieval.go | 2 +- ffs/integrationtest/manager/manager.go | 4 +- ffs/manager/manager.go | 2 +- ffs/scheduler/internal/astore/astore.go | 1 + ffs/scheduler/internal/rjstore/rjstore.go | 13 +- .../internal/rjstore/rjstore_test.go | 2 +- ffs/scheduler/internal/sjstore/sjstore.go | 12 +- ffs/scheduler/scheduler.go | 21 +- ffs/scheduler/scheduler_retrieval.go | 3 +- ffs/scheduler/scheduler_storage.go | 2 +- notifications/events.go | 49 +++ notifications/final_job_status.go | 90 ++++++ notifications/job_updates.go | 16 + notifications/notifier.go | 283 ++++++------------ notifications/retrieval_job.go | 85 ++++++ notifications/storage_job.go | 96 ++++++ notifications/store.go | 42 +++ tests/txmapds.go | 8 +- 19 files changed, 523 insertions(+), 213 deletions(-) create mode 100644 notifications/events.go create mode 100644 notifications/final_job_status.go create mode 100644 notifications/job_updates.go create mode 100644 notifications/retrieval_job.go create mode 100644 notifications/storage_job.go create mode 100644 notifications/store.go diff --git a/api/server/server.go b/api/server/server.go index 183135c5f..7c0dc1884 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -50,7 +50,6 @@ import ( "github.com/textileio/powergate/v2/iplocation/maxmind" "github.com/textileio/powergate/v2/lotus" "github.com/textileio/powergate/v2/migration" - "github.com/textileio/powergate/v2/notifications" "github.com/textileio/powergate/v2/reputation" txndstr "github.com/textileio/powergate/v2/txndstransform" "github.com/textileio/powergate/v2/util" @@ -254,8 +253,6 @@ func NewServer(conf Config) (*Server, error) { conf.DealWatchPollDuration = time.Second } - notifier := notifications.New() - log.Info("Starting deals module...") dm, err := dealsModule.New(txndstr.Wrap(ds, "deals"), clientBuilder, conf.DealWatchPollDuration, conf.FFSDealFinalityTimeout, deals.WithImportPath(filepath.Join(conf.RepoPath, "imports"))) if err != nil { @@ -297,7 +294,7 @@ func NewServer(conf Config) (*Server, error) { sr2rf = ms.GetReplicationFactor } gcConfig := scheduler.GCConfig{StageGracePeriod: conf.FFSGCStageGracePeriod, AutoGCInterval: conf.FFSGCAutomaticGCInterval} - sched, err := scheduler.New(txndstr.Wrap(ds, "ffs/scheduler"), l, hs, cs, conf.SchedMaxParallel, conf.FFSDealFinalityTimeout, sr2rf, gcConfig, notifier) + sched, err := scheduler.New(txndstr.Wrap(ds, "ffs/scheduler"), l, hs, cs, conf.SchedMaxParallel, conf.FFSDealFinalityTimeout, sr2rf, gcConfig) if err != nil { return nil, fmt.Errorf("creating scheduler: %s", err) } diff --git a/ffs/api/api_retrieval.go b/ffs/api/api_retrieval.go index d9191359e..a3d05eca0 100644 --- a/ffs/api/api_retrieval.go +++ b/ffs/api/api_retrieval.go @@ -56,7 +56,7 @@ func (i *API) StartRetrieval(payloadCid, pieceCid cid.Cid, selector string, mine } rID := ffs.NewRetrievalID() - jid, err := i.sched.StartRetrieval(i.cfg.ID, rID, payloadCid, pieceCid, selector, miners, rc.walletAddress, rc.maxPrice) + jid, err := i.sched.StartRetrieval(i.cfg.ID, rID, payloadCid, pieceCid, selector, miners, rc.walletAddress, rc.maxPrice, i.cfg.DefaultStorageConfig.Notifications) if err != nil { return Retrieval{}, fmt.Errorf("starting retrieval in scheduler: %s", err) } diff --git a/ffs/integrationtest/manager/manager.go b/ffs/integrationtest/manager/manager.go index 7871b5d66..906dfb8ae 100644 --- a/ffs/integrationtest/manager/manager.go +++ b/ffs/integrationtest/manager/manager.go @@ -20,7 +20,6 @@ import ( "github.com/textileio/powergate/v2/ffs/scheduler" "github.com/textileio/powergate/v2/filchain" "github.com/textileio/powergate/v2/lotus" - "github.com/textileio/powergate/v2/notifications" "github.com/textileio/powergate/v2/tests" "github.com/textileio/powergate/v2/util" @@ -80,7 +79,6 @@ func NewFFSManager(t require.TestingT, ds datastore.TxnDatastore, clientBuilder // NewCustomFFSManager returns a new customized FFS manager. func NewCustomFFSManager(t require.TestingT, ds datastore.TxnDatastore, cb lotus.ClientBuilder, masterAddr address.Address, ms ffs.MinerSelector, ipfsClient *httpapi.HttpApi, minimumPieceSize uint64) (*manager.Manager, *coreipfs.CoreIpfs, func()) { - notifier := notifications.New() dm, err := dealsModule.New(txndstr.Wrap(ds, "deals"), cb, util.AvgBlockTime, time.Minute*10) require.NoError(t, err) @@ -91,7 +89,7 @@ func NewCustomFFSManager(t require.TestingT, ds datastore.TxnDatastore, cb lotus cl := filcold.New(ms, dm, nil, ipfsClient, fchain, l, lsm, minimumPieceSize, 1, time.Hour) hl, err := coreipfs.New(ds, ipfsClient, l) require.NoError(t, err) - sched, err := scheduler.New(txndstr.Wrap(ds, "ffs/scheduler"), l, hl, cl, 10, time.Minute*10, nil, scheduler.GCConfig{AutoGCInterval: 0}, notifier) + sched, err := scheduler.New(txndstr.Wrap(ds, "ffs/scheduler"), l, hl, cl, 10, time.Minute*10, nil, scheduler.GCConfig{AutoGCInterval: 0}) require.NoError(t, err) wm, err := lotusWallet.New(cb, masterAddr, *big.NewInt(iWalletBal), false, "") diff --git a/ffs/manager/manager.go b/ffs/manager/manager.go index 0a2712b7c..6e65423ee 100644 --- a/ffs/manager/manager.go +++ b/ffs/manager/manager.go @@ -71,7 +71,7 @@ var ( Endpoint: "https://vmanilo.free.beeceptor.com/webhook/job", }, Configuration: &ffs.WebhookConfiguration{ - Events: []string{"storage-deal-created"}, + Events: []string{"*-created", "*-completed"}, }, }, }, diff --git a/ffs/scheduler/internal/astore/astore.go b/ffs/scheduler/internal/astore/astore.go index 33087d998..cd4627481 100644 --- a/ffs/scheduler/internal/astore/astore.go +++ b/ffs/scheduler/internal/astore/astore.go @@ -38,6 +38,7 @@ type RetrievalAction struct { Miners []string WalletAddress string MaxPrice uint64 + Notifications []*ffs.NotificationConfig } // Store persists Actions. diff --git a/ffs/scheduler/internal/rjstore/rjstore.go b/ffs/scheduler/internal/rjstore/rjstore.go index a08b31102..b316c163b 100644 --- a/ffs/scheduler/internal/rjstore/rjstore.go +++ b/ffs/scheduler/internal/rjstore/rjstore.go @@ -11,6 +11,7 @@ import ( "github.com/ipfs/go-datastore/query" logging "github.com/ipfs/go-log/v2" "github.com/textileio/powergate/v2/ffs" + "github.com/textileio/powergate/v2/notifications" ) var ( @@ -26,6 +27,7 @@ type Store struct { lock sync.Mutex ds datastore.Datastore watchers []watcher + notifier notifications.Notifier } // watcher represents an API instance who is watching for @@ -36,8 +38,8 @@ type watcher struct { } // New returns a new retrieval job store. -func New(ds datastore.Datastore) (*Store, error) { - s := &Store{ds: ds} +func New(ds datastore.Datastore, notifier notifications.Notifier) (*Store, error) { + s := &Store{ds: ds, notifier: notifier} return s, nil } @@ -62,6 +64,13 @@ func (s *Store) Finalize(jid ffs.JobID, st ffs.JobStatus, jobError error) error if jobError != nil { j.ErrCause = jobError.Error() } + + s.notifier.NotifyJobUpdates(¬ifications.FinalJobStatus{ + JobId: jid, + JobStatus: st, + JobError: jobError, + }) + if err := s.put(j); err != nil { return fmt.Errorf("saving in datastore: %s", err) } diff --git a/ffs/scheduler/internal/rjstore/rjstore_test.go b/ffs/scheduler/internal/rjstore/rjstore_test.go index a149f6fc5..a902557b5 100644 --- a/ffs/scheduler/internal/rjstore/rjstore_test.go +++ b/ffs/scheduler/internal/rjstore/rjstore_test.go @@ -53,7 +53,7 @@ func createJob() ffs.RetrievalJob { func create(t *testing.T) *Store { ds := tests.NewTxMapDatastore() - store, err := New(ds) + store, err := New(ds, tests.NewMockNotifier()) require.NoError(t, err) return store } diff --git a/ffs/scheduler/internal/sjstore/sjstore.go b/ffs/scheduler/internal/sjstore/sjstore.go index 3616f58b9..9be8867af 100644 --- a/ffs/scheduler/internal/sjstore/sjstore.go +++ b/ffs/scheduler/internal/sjstore/sjstore.go @@ -125,7 +125,10 @@ func (s *Store) MonitorJob(j ffs.StorageJob) chan deals.StorageDealInfo { continue } - s.notifier.NotifyStorageJob(job, update) + s.notifier.NotifyJobUpdates(¬ifications.StorageJobUpdates{ + Job: job, + Info: update, + }) values := make([]deals.StorageDealInfo, 0, len(s.jobStatusCache[j.APIID][j.Cid])) for _, v := range s.jobStatusCache[j.APIID][j.Cid] { @@ -157,6 +160,13 @@ func (s *Store) Finalize(jid ffs.JobID, st ffs.JobStatus, jobError error, dealEr return err } + s.notifier.NotifyJobUpdates(¬ifications.FinalJobStatus{ + JobId: jid, + JobStatus: st, + JobError: jobError, + DealErrors: dealErrors, + }) + ctx := context.Background() s.metricJobCounter.Add(ctx, -1, attrStatusExecuting) switch st { diff --git a/ffs/scheduler/scheduler.go b/ffs/scheduler/scheduler.go index 594c2395d..aabac0fc6 100644 --- a/ffs/scheduler/scheduler.go +++ b/ffs/scheduler/scheduler.go @@ -91,25 +91,30 @@ type GCConfig struct { // New returns a new instance of Scheduler which uses JobStore as its backing repository for state, // HotStorage for the hot layer, and ColdStorage for the cold layer. -func New(ds datastore.TxnDatastore, l ffs.JobLogger, hs ffs.HotStorage, cs ffs.ColdStorage, maxParallel int, dealFinalityTimeout time.Duration, sr2rf func() (int, error), gcConfig GCConfig, notifier notifications.Notifier) (*Scheduler, error) { +func New(ds datastore.TxnDatastore, l ffs.JobLogger, hs ffs.HotStorage, cs ffs.ColdStorage, maxParallel int, dealFinalityTimeout time.Duration, sr2rf func() (int, error), gcConfig GCConfig) (*Scheduler, error) { + ctx, cancel := context.WithCancel(context.Background()) + notifier := notifications.New(ctx) + sjs, err := sjstore.New(txndstr.Wrap(ds, "sjstore"), notifier) if err != nil { + cancel() return nil, fmt.Errorf("loading stroage jobstore: %s", err) } - rjs, err := rjstore.New(txndstr.Wrap(ds, "rjstore")) + rjs, err := rjstore.New(txndstr.Wrap(ds, "rjstore"), notifier) if err != nil { + cancel() return nil, fmt.Errorf("loading retrieval jobstore: %s", err) } as := astore.New(txndstr.Wrap(ds, "astore")) ts, err := trackstore.New(txndstr.Wrap(ds, "tstore")) if err != nil { + cancel() return nil, fmt.Errorf("loading scheduler trackstore: %s", err) } cis := cistore.New(txndstr.Wrap(ds, "cistore_v2")) ris := ristore.New(txndstr.Wrap(ds, "ristore")) - ctx, cancel := context.WithCancel(context.Background()) sch := &Scheduler{ cs: cs, hs: hs, @@ -524,7 +529,7 @@ func (s *Scheduler) executeQueuedStorage(j ffs.StorageJob) { return } - s.notifier.RegisterStorageJob(j, a.Cfg.Notifications) + s.notifier.RegisterJob(j.ID, a.Cfg.Notifications) // Execute s.l.Log(ctx, "Executing job %s...", j.ID) @@ -640,6 +645,8 @@ func (s *Scheduler) executeQueuedRetrievals(j ffs.RetrievalJob) { return } + s.notifier.RegisterJob(j.ID, a.Notifications) + // Execute s.l.Log(ctx, "Executing job %s...", j.ID) info, err := s.executeRetrieval(ctx, a, j) @@ -654,6 +661,12 @@ func (s *Scheduler) executeQueuedRetrievals(j ffs.RetrievalJob) { s.l.Log(ctx, "Job %s execution failed: %s", j.ID, err) return } + + s.notifier.NotifyJobUpdates(¬ifications.RetrievalJobUpdates{ + Job: j, + Info: info, + }) + // Save whatever stored information was completely/partially // done in execution. if err := s.ris.Put(info); err != nil { diff --git a/ffs/scheduler/scheduler_retrieval.go b/ffs/scheduler/scheduler_retrieval.go index e93965f69..34b1fe8fa 100644 --- a/ffs/scheduler/scheduler_retrieval.go +++ b/ffs/scheduler/scheduler_retrieval.go @@ -11,7 +11,7 @@ import ( ) // StartRetrieval schedules a new RetrievalJob to execute a Filecoin retrieval. -func (s *Scheduler) StartRetrieval(iid ffs.APIID, rid ffs.RetrievalID, pyCid, piCid cid.Cid, sel string, miners []string, walletAddr string, maxPrice uint64) (ffs.JobID, error) { +func (s *Scheduler) StartRetrieval(iid ffs.APIID, rid ffs.RetrievalID, pyCid, piCid cid.Cid, sel string, miners []string, walletAddr string, maxPrice uint64, notifications []*ffs.NotificationConfig) (ffs.JobID, error) { if iid == ffs.EmptyInstanceID { return ffs.EmptyJobID, fmt.Errorf("empty API ID") } @@ -52,6 +52,7 @@ func (s *Scheduler) StartRetrieval(iid ffs.APIID, rid ffs.RetrievalID, pyCid, pi Miners: miners, WalletAddress: walletAddr, MaxPrice: maxPrice, + Notifications: notifications, } if err := s.as.PutRetrievalAction(j.ID, ra); err != nil { return ffs.EmptyJobID, fmt.Errorf("saving retrieval action for job: %s", err) diff --git a/ffs/scheduler/scheduler_storage.go b/ffs/scheduler/scheduler_storage.go index f5b626cf5..265b14a66 100644 --- a/ffs/scheduler/scheduler_storage.go +++ b/ffs/scheduler/scheduler_storage.go @@ -82,7 +82,7 @@ func (s *Scheduler) push(iid ffs.APIID, c cid.Cid, cfg ffs.StorageConfig, oldCid s.l.Log(ctx, "Configuration saved successfully") - s.notifier.RegisterStorageJob(j, cfg.Notifications) + s.notifier.RegisterJob(j.ID, cfg.Notifications) return jid, nil } diff --git a/notifications/events.go b/notifications/events.go new file mode 100644 index 000000000..41a370823 --- /dev/null +++ b/notifications/events.go @@ -0,0 +1,49 @@ +package notifications + +const ( + all = "*" + created = "created" + completed = "completed" + retried = "retried" + failed = "failed" + canceled = "canceled" + expired = "expired" + slashed = "slashed" + separator = "-" + storageDeal = "storage-deal" + storageAuction = "storage-auction" + dataRetrieval = "data-retrieval" + + // All events + AllEvents = all + AllCreatedEvents = all + separator + created + AllCompletedEvents = all + separator + completed + AllRetriedEvents = all + separator + retried + AllFailedEvents = all + separator + failed + AllCanceledEvents = all + separator + canceled + + // Storage deal events + AllStorageDealEvents = storageDeal + separator + all + StorageDealCreatedEvent = storageDeal + separator + created + StorageDealCompletedEvent = storageDeal + separator + completed + StorageDealRetriedEvent = storageDeal + separator + retried + StorageDealFailedEvent = storageDeal + separator + failed + StorageDealCanceledEvent = storageDeal + separator + canceled + StorageDealExpiredEvent = storageDeal + separator + expired + StorageDealSlashedEvent = storageDeal + separator + slashed + + // Storage auction events + AllStorageAuctionEvents = storageAuction + separator + all + StorageAuctionCreatedEvent = storageAuction + separator + created + StorageAuctionCompletedEvent = storageAuction + separator + completed + StorageAuctionFailedEvent = storageAuction + separator + failed + StorageAuctionCanceledEvent = storageAuction + separator + canceled + + // Data retrieval events + AllDataRetrievalEvents = dataRetrieval + separator + all + DataRetrievalCreatedEvent = dataRetrieval + separator + created + DataRetrievalCompletedEvent = dataRetrieval + separator + completed + DataRetrievalRetriedEvent = dataRetrieval + separator + retried + DataRetrievalFailedEvent = dataRetrieval + separator + failed + DataRetrievalCanceledEvent = dataRetrieval + separator + canceled +) diff --git a/notifications/final_job_status.go b/notifications/final_job_status.go new file mode 100644 index 000000000..b3f441cf7 --- /dev/null +++ b/notifications/final_job_status.go @@ -0,0 +1,90 @@ +package notifications + +import ( + "bytes" + "encoding/json" + "io" + + "github.com/textileio/powergate/v2/ffs" +) + +type FinalJobStatus struct { + JobId ffs.JobID + JobStatus ffs.JobStatus + JobError error + DealErrors []ffs.DealError +} + +func (f FinalJobStatus) JobID() ffs.JobID { + return f.JobId +} + +func (f FinalJobStatus) FinalUpdates() bool { + return true +} + +func (f FinalJobStatus) MatchNotificationAlert(alert *ffs.WebhookAlert) bool { + return false +} + +func (f FinalJobStatus) MatchNotificationEvent(event string) bool { + switch event { + case AllEvents, AllStorageDealEvents: + return true + + case AllCompletedEvents, StorageDealCompletedEvent: + return f.JobStatus == ffs.Success + + case AllFailedEvents, StorageDealFailedEvent: + return f.JobStatus == ffs.Failed + + case AllCanceledEvents, StorageDealCanceledEvent: + return f.JobStatus == ffs.Canceled + + default: + return false + } +} + +type finalStorageJobNotification struct { + JobId ffs.JobID `json:"jobId"` + JobStatus string `json:"jobStatus"` + JobError string `json:"jobError,omitempty"` + DealErrors []dealError `json:"dealErrors,omitempty"` +} + +type dealError struct { + ProposalCid string `json:"proposalCid"` + Miner string `json:"miner"` + Message string `json:"error"` +} + +func (f FinalJobStatus) Payload() (io.Reader, error) { + var errMessage string + if f.JobError != nil { + errMessage = f.JobError.Error() + } + + var dealErrors []dealError + for _, deal := range f.DealErrors { + dealErrors = append(dealErrors, dealError{ + ProposalCid: deal.ProposalCid.String(), + Miner: deal.Miner, + Message: deal.Message, + }) + } + + obj := &finalStorageJobNotification{ + JobId: f.JobId, + JobStatus: ffs.JobStatusStr[f.JobStatus], + JobError: errMessage, + DealErrors: dealErrors, + } + + data, err := json.Marshal(obj) + if err != nil { + return nil, err + } + + return bytes.NewBuffer(data), nil +} diff --git a/notifications/job_updates.go b/notifications/job_updates.go new file mode 100644 index 000000000..37dd1b450 --- /dev/null +++ b/notifications/job_updates.go @@ -0,0 +1,16 @@ +package notifications + +import ( + "io" + + "github.com/textileio/powergate/v2/ffs" +) + +type JobUpdates interface { + JobID() ffs.JobID + FinalUpdates() bool + + Payload() (io.Reader, error) + MatchNotificationEvent(event string) bool + MatchNotificationAlert(alert *ffs.WebhookAlert) bool +} diff --git a/notifications/notifier.go b/notifications/notifier.go index a5413a96a..a0673462e 100644 --- a/notifications/notifier.go +++ b/notifications/notifier.go @@ -1,91 +1,110 @@ package notifications import ( - "bytes" - "encoding/json" + "context" + "io" "net/http" - "sync" - "github.com/filecoin-project/go-fil-markets/storagemarket" - "github.com/textileio/powergate/v2/deals" + logging "github.com/ipfs/go-log/v2" "github.com/textileio/powergate/v2/ffs" ) -type Notifier interface { - RegisterStorageJob(job ffs.StorageJob, notificationConfig []*ffs.NotificationConfig) - NotifyStorageJob(job ffs.StorageJob, dealInfo deals.StorageDealInfo) -} +var ( + log = logging.Logger("notifier") +) -type storageJobUpdate struct { - job ffs.StorageJob - dealInfo deals.StorageDealInfo +type Notifier interface { + RegisterJob(jobId ffs.JobID, configs []*ffs.NotificationConfig) + NotifyJobUpdates(job JobUpdates) } type notifier struct { - client *http.Client - store *configStore - storageJobNotifications chan *storageJobUpdate + ctx context.Context + configs *configStore + updates chan JobUpdates + toDelete chan ffs.JobID + notifications chan *notification } -func New() *notifier { +func New(ctx context.Context) *notifier { nt := ¬ifier{ - client: http.DefaultClient, - store: newConfigStore(), - storageJobNotifications: make(chan *storageJobUpdate, 1000), + ctx: ctx, + configs: newConfigStore(), + updates: make(chan JobUpdates, 1000), + toDelete: make(chan ffs.JobID, 1000), + notifications: make(chan *notification, 1000), } go nt.run() + const workers = 10 + for i := 0; i < workers; i++ { + go nt.worker() + } + return nt } -func (n *notifier) RegisterStorageJob(job ffs.StorageJob, notificationConfig []*ffs.NotificationConfig) { - if notificationConfig == nil { +func (n *notifier) RegisterJob(jobId ffs.JobID, configs []*ffs.NotificationConfig) { + if configs == nil { return } - n.store.put(job, notificationConfig) + n.configs.put(jobId, configs) } -func (n *notifier) NotifyStorageJob(job ffs.StorageJob, dealInfo deals.StorageDealInfo) { - n.storageJobNotifications <- &storageJobUpdate{ - job: job, - dealInfo: dealInfo, - } +func (n *notifier) NotifyJobUpdates(jobUpdates JobUpdates) { + n.updates <- jobUpdates } func (n *notifier) run() { - for updates := range n.storageJobNotifications { - if updates == nil { - continue - } - - config := n.store.get(updates.job.ID) - if config == nil { - continue + for { + select { + case <-n.ctx.Done(): + return + + case updates, ok := <-n.updates: + if !ok { + return + } + + config := n.configs.get(updates.JobID()) + if config == nil { + continue + } + + n.notifyAll(config, updates) + + if updates.FinalUpdates() { + n.configs.delete(updates.JobID()) + } } - - n.notifyAll(config.notifications, updates) } } -func (n *notifier) notifyAll(configs []*ffs.NotificationConfig, updates *storageJobUpdate) { +func (n *notifier) notifyAll(configs []*ffs.NotificationConfig, updates JobUpdates) { for _, cfg := range configs { + if cfg == nil { + continue + } + n.notify(cfg, updates) } } -func (n *notifier) notify(config *ffs.NotificationConfig, updates *storageJobUpdate) { - if config == nil { - return - } +func (n *notifier) notify(config *ffs.NotificationConfig, updates JobUpdates) { + if matchNotificationConfig(config.Configuration, updates) { + payload, err := updates.Payload() + if err != nil { + log.Errorf("failed to make notification payload: %s", err) + return + } - if matchNotificationConfig(config.Configuration, updates.dealInfo) { - n.publishNotification(config.Webhook, updates) + n.publishNotification(config.Webhook, payload) } } -func matchNotificationConfig(config *ffs.WebhookConfiguration, updates deals.StorageDealInfo) bool { +func matchNotificationConfig(config *ffs.WebhookConfiguration, updates JobUpdates) bool { if config == nil { return false } @@ -93,47 +112,9 @@ func matchNotificationConfig(config *ffs.WebhookConfiguration, updates deals.Sto return matchNotificationEvents(config.Events, updates) || matchNotificationAlerts(config.Alerts, updates) } -const ( - all = "*" - created = "created" - completed = "completed" - retried = "retried" - failed = "failed" - expired = "expired" - slashed = "slashed" - separator = "-" - storageDeal = "storage-deal" - storageAuction = "storage-auction" - dataRetrieval = "data-retrieval" - - AllEvents = all - AllCreatedEvents = all + separator + created - AllCompletedEvents = all + separator + completed - AllRetriedEvents = all + separator + retried - AllFailedEvents = all + separator + failed - - AllStorageDealEvents = storageDeal + separator + all - StorageDealCreatedEvent = storageDeal + separator + created - StorageDealCompletedEvent = storageDeal + separator + completed - StorageDealRetriedEvent = storageDeal + separator + retried - StorageDealFailedEvent = storageDeal + separator + failed - StorageDealExpiredEvent = storageDeal + separator + expired - StorageDealSlashedEvent = storageDeal + separator + slashed - - AllStorageAuctionEvents = storageAuction + separator + all - StorageAuctionCreatedEvent = storageAuction + separator + created - StorageAuctionCompletedEvent = storageAuction + separator + completed - StorageAuctionFailedEvent = storageAuction + separator + failed - - AllDataRetrievalEvents = dataRetrieval + separator + all - DataRetrievalCompletedEvent = dataRetrieval + separator + completed - DataRetrievalRetriedEvent = dataRetrieval + separator + retried - DataRetrievalFailedEvent = dataRetrieval + separator + failed -) - -func matchNotificationEvents(events []string, updates deals.StorageDealInfo) bool { +func matchNotificationEvents(events []string, updates JobUpdates) bool { for _, event := range events { - if matchNotificationEvent(event, updates) { + if updates.MatchNotificationEvent(event) { return true } } @@ -141,125 +122,49 @@ func matchNotificationEvents(events []string, updates deals.StorageDealInfo) boo return false } -func matchNotificationEvent(event string, updates deals.StorageDealInfo) bool { - switch event { - case AllEvents: - return true - case AllCreatedEvents: - // TODO: add created events - return updates.DealID != 0 // || - case AllCompletedEvents: - // TODO: add other completed events - return updates.StateID == storagemarket.StorageDealActive // || - - // TODO: - // case AllRetriedEvents: - - case AllStorageDealEvents: - return true - - case StorageDealCreatedEvent: - return updates.DealID != 0 - - case StorageDealCompletedEvent: - return updates.StateID == storagemarket.StorageDealActive - - // TODO: - // case StorageDealRetriedEvent: - - case StorageDealFailedEvent: - return updates.StateID == storagemarket.StorageDealFailing || updates.StateID == storagemarket.StorageDealError || updates.Message != "" - - case StorageDealExpiredEvent: - return updates.StateID == storagemarket.StorageDealExpired - - case StorageDealSlashedEvent: - return updates.StateID == storagemarket.StorageDealSlashed - - default: - return false +func matchNotificationAlerts(alerts []*ffs.WebhookAlert, updates JobUpdates) bool { + for _, alert := range alerts { + if updates.MatchNotificationAlert(alert) { + return true + } } -} -func matchNotificationAlerts(alerts []*ffs.WebhookAlert, updates deals.StorageDealInfo) bool { - // TODO return false } -type notification struct { - Cid string `json:"cid"` - JobID ffs.JobID `json:"jobId"` - JobStatus string `json:"jobStatus"` - Miner string `json:"miner"` - Price uint64 `json:"price"` - ProposalCid string `json:"proposalCid"` - DealID uint64 `json:"dealId,omitempty"` - DealStatus string `json:"dealStatus"` - ErrCause string `json:"error,omitempty"` - Message string `json:"message,omitempty"` -} - -func (n *notifier) publishNotification(webhook *ffs.Webhook, updates *storageJobUpdate) { - if webhook == nil { - return - } - - obj := ¬ification{ - Cid: updates.job.Cid.String(), - JobID: updates.job.ID, - JobStatus: ffs.JobStatusStr[updates.job.Status], - Miner: updates.dealInfo.Miner, - Price: updates.dealInfo.PricePerEpoch, - ProposalCid: updates.dealInfo.ProposalCid.String(), - DealID: updates.dealInfo.DealID, - DealStatus: updates.dealInfo.StateName, - ErrCause: updates.job.ErrCause, - Message: updates.dealInfo.Message, - } - - data, err := json.Marshal(obj) - if err != nil { - // TODO: log error +func (n *notifier) publishNotification(webhook *ffs.Webhook, payload io.Reader) { + if webhook == nil || payload == nil { return } - err = webhook.Publish(n.client, bytes.NewBuffer(data)) - if err != nil { - // TODO: log error - return + n.notifications <- ¬ification{ + webhook: webhook, + payload: payload, } } -type configStore struct { - sync.RWMutex - - configs map[ffs.JobID]*jobConfig +type notification struct { + webhook *ffs.Webhook + payload io.Reader } -func newConfigStore() *configStore { - return &configStore{ - configs: make(map[ffs.JobID]*jobConfig), - } -} +func (n *notifier) worker() { + client := http.DefaultClient -type jobConfig struct { - job ffs.StorageJob - notifications []*ffs.NotificationConfig -} + for { + select { + case <-n.ctx.Done(): + return -func (s *configStore) put(job ffs.StorageJob, notifications []*ffs.NotificationConfig) { - s.Lock() - defer s.Unlock() + case notification, ok := <-n.notifications: + if !ok { + return + } - s.configs[job.ID] = &jobConfig{ - job: job, - notifications: notifications, + err := notification.webhook.Publish(client, notification.payload) + if err != nil { + log.Errorf("failed to publish notification: %s", err) + } + } } } - -func (s *configStore) get(jobID ffs.JobID) *jobConfig { - s.RLock() - defer s.RUnlock() - - return s.configs[jobID] -} diff --git a/notifications/retrieval_job.go b/notifications/retrieval_job.go new file mode 100644 index 000000000..a0848373c --- /dev/null +++ b/notifications/retrieval_job.go @@ -0,0 +1,85 @@ +package notifications + +import ( + "bytes" + "encoding/json" + "io" + "time" + + "github.com/textileio/powergate/v2/ffs" +) + +type RetrievalJobUpdates struct { + Job ffs.RetrievalJob + Info ffs.RetrievalInfo +} + +func (r RetrievalJobUpdates) JobID() ffs.JobID { + return r.Job.ID +} + +func (r RetrievalJobUpdates) FinalUpdates() bool { + return false +} + +type retrievalJobNotification struct { + JobID ffs.JobID `json:"jobId"` + JobStatus string `json:"jobStatus"` + RetrievalID ffs.RetrievalID `json:"retrievalId"` + DataCid string `json:"dataCid"` + TotalPaid uint64 `json:"totalPaid"` + Miner string `json:"miner"` + Size int64 `json:"size"` + CreatedAt string `json:"createdAt"` + ErrCause string `json:"error,omitempty"` +} + +func (r RetrievalJobUpdates) Payload() (io.Reader, error) { + obj := &retrievalJobNotification{ + JobID: r.Job.ID, + JobStatus: ffs.JobStatusStr[r.Job.Status], + RetrievalID: r.Job.RetrievalID, + DataCid: r.Info.DataCid.String(), + TotalPaid: r.Info.TotalPaid, + Miner: r.Info.MinerAddr, + Size: r.Info.Size, + CreatedAt: r.Info.CreatedAt.Format(time.RFC3339), + ErrCause: r.Job.ErrCause, + } + + data, err := json.Marshal(obj) + if err != nil { + return nil, err + } + + return bytes.NewBuffer(data), nil +} + +func (r RetrievalJobUpdates) MatchNotificationEvent(event string) bool { + switch event { + case AllEvents, AllDataRetrievalEvents: + return true + case AllCreatedEvents, DataRetrievalCreatedEvent: + return r.Job.Status == ffs.Executing + case AllCompletedEvents, DataRetrievalCompletedEvent: + return r.Job.Status == ffs.Success + + case AllRetriedEvents, DataRetrievalRetriedEvent: + // TODO: + return false + + case AllFailedEvents, DataRetrievalFailedEvent: + return r.Job.Status == ffs.Failed + + case AllCanceledEvents, DataRetrievalCanceledEvent: + return r.Job.Status == ffs.Canceled + + default: + return false + } +} + +func (r RetrievalJobUpdates) MatchNotificationAlert(alert *ffs.WebhookAlert) bool { + // TODO + return false +} diff --git a/notifications/storage_job.go b/notifications/storage_job.go new file mode 100644 index 000000000..0fb33ceb4 --- /dev/null +++ b/notifications/storage_job.go @@ -0,0 +1,96 @@ +package notifications + +import ( + "bytes" + "encoding/json" + "io" + + "github.com/filecoin-project/go-fil-markets/storagemarket" + "github.com/textileio/powergate/v2/deals" + "github.com/textileio/powergate/v2/ffs" +) + +type StorageJobUpdates struct { + Job ffs.StorageJob + Info deals.StorageDealInfo +} + +func (s StorageJobUpdates) JobID() ffs.JobID { + return s.Job.ID +} + +func (s StorageJobUpdates) FinalUpdates() bool { + return false +} + +func (s StorageJobUpdates) MatchNotificationAlert(alert *ffs.WebhookAlert) bool { + // TODO: + return false +} + +func (s StorageJobUpdates) MatchNotificationEvent(event string) bool { + switch event { + case AllEvents, AllStorageDealEvents: + return true + case AllCreatedEvents, StorageDealCreatedEvent: + return s.Info.DealID != 0 + case AllCompletedEvents, StorageDealCompletedEvent: + return s.Info.StateID == storagemarket.StorageDealActive + + case AllRetriedEvents, StorageDealRetriedEvent: + // TODO: + return false + + case AllFailedEvents, StorageDealFailedEvent: + return s.Job.Status == ffs.Failed || + s.Info.StateID == storagemarket.StorageDealFailing || + s.Info.StateID == storagemarket.StorageDealError + + case AllCanceledEvents, StorageDealCanceledEvent: + return s.Job.Status == ffs.Canceled + + case StorageDealExpiredEvent: + return s.Info.StateID == storagemarket.StorageDealExpired + + case StorageDealSlashedEvent: + return s.Info.StateID == storagemarket.StorageDealSlashed + + default: + return false + } +} + +type storageJobNotification struct { + Cid string `json:"cid"` + JobID ffs.JobID `json:"jobId"` + JobStatus string `json:"jobStatus"` + Miner string `json:"miner"` + Price uint64 `json:"price"` + ProposalCid string `json:"proposalCid"` + DealID uint64 `json:"dealId,omitempty"` + DealStatus string `json:"dealStatus"` + ErrCause string `json:"error,omitempty"` + Message string `json:"message,omitempty"` +} + +func (s StorageJobUpdates) Payload() (io.Reader, error) { + obj := &storageJobNotification{ + Cid: s.Job.Cid.String(), + JobID: s.Job.ID, + JobStatus: ffs.JobStatusStr[s.Job.Status], + Miner: s.Info.Miner, + Price: s.Info.PricePerEpoch, + ProposalCid: s.Info.ProposalCid.String(), + DealID: s.Info.DealID, + DealStatus: s.Info.StateName, + ErrCause: s.Job.ErrCause, + Message: s.Info.Message, + } + + data, err := json.Marshal(obj) + if err != nil { + return nil, err + } + + return bytes.NewBuffer(data), nil +} diff --git a/notifications/store.go b/notifications/store.go new file mode 100644 index 000000000..78e30d322 --- /dev/null +++ b/notifications/store.go @@ -0,0 +1,42 @@ +package notifications + +import ( + "sync" + + "github.com/textileio/powergate/v2/ffs" +) + +type configStore struct { + sync.RWMutex + + configs map[ffs.JobID][]*ffs.NotificationConfig +} + +func newConfigStore() *configStore { + return &configStore{ + configs: make(map[ffs.JobID][]*ffs.NotificationConfig), + } +} + +func (s *configStore) put(jobId ffs.JobID, configs []*ffs.NotificationConfig) { + s.Lock() + defer s.Unlock() + + s.configs[jobId] = configs +} + +func (s *configStore) get(jobId ffs.JobID) []*ffs.NotificationConfig { + s.RLock() + defer s.RUnlock() + + return s.configs[jobId] +} + +func (s *configStore) delete(jobId ffs.JobID) { + s.Lock() + defer s.Unlock() + + if _, ok := s.configs[jobId]; ok { + delete(s.configs, jobId) + } +} diff --git a/tests/txmapds.go b/tests/txmapds.go index 95bd7b29d..ed82d3916 100644 --- a/tests/txmapds.go +++ b/tests/txmapds.go @@ -6,8 +6,8 @@ import ( "github.com/ipfs/go-datastore" "github.com/ipfs/go-datastore/query" - "github.com/textileio/powergate/v2/deals" "github.com/textileio/powergate/v2/ffs" + "github.com/textileio/powergate/v2/notifications" ) // TxMapDatastore is a in-memory datastore that satisfies TxnDatastore. @@ -181,7 +181,5 @@ func NewMockNotifier() *mockNotifier { return &mockNotifier{} } -func (n *mockNotifier) NotifyDealRecord(dr deals.StorageDealRecord) {} -func (n *mockNotifier) RegisterStorageJob(job ffs.StorageJob, notificationConfig []*ffs.NotificationConfig) { -} -func (n *mockNotifier) NotifyStorageJob(jobID ffs.JobID, dealInfo deals.StorageDealInfo) {} +func (n *mockNotifier) RegisterJob(jobId ffs.JobID, configs []*ffs.NotificationConfig) {} +func (n *mockNotifier) NotifyJobUpdates(job notifications.JobUpdates) {} From 1fe306fb2451d9285c68cb0258777740b10881f7 Mon Sep 17 00:00:00 2001 From: Volodymyr Manilo Date: Mon, 27 Jun 2022 23:19:29 +0300 Subject: [PATCH 5/7] added datacap alert --- ffs/manager/manager.go | 6 ++ ffs/scheduler/scheduler.go | 2 + ffs/scheduler/scheduler_storage.go | 2 + notifications/alerts.go | 105 +++++++++++++++++++++++++++++ notifications/events.go | 30 +++------ notifications/final_job_status.go | 4 +- notifications/job_updates.go | 8 ++- notifications/notifier.go | 48 ++++++++++--- notifications/retrieval_job.go | 5 +- notifications/storage_job.go | 5 +- 10 files changed, 175 insertions(+), 40 deletions(-) create mode 100644 notifications/alerts.go diff --git a/ffs/manager/manager.go b/ffs/manager/manager.go index 6e65423ee..e31b182e9 100644 --- a/ffs/manager/manager.go +++ b/ffs/manager/manager.go @@ -72,6 +72,12 @@ var ( }, Configuration: &ffs.WebhookConfiguration{ Events: []string{"*-created", "*-completed"}, + Alerts: []*ffs.WebhookAlert{ + { + Type: "datacap", + Threshold: "500 GB", + }, + }, }, }, }, diff --git a/ffs/scheduler/scheduler.go b/ffs/scheduler/scheduler.go index aabac0fc6..c765f9658 100644 --- a/ffs/scheduler/scheduler.go +++ b/ffs/scheduler/scheduler.go @@ -530,6 +530,7 @@ func (s *Scheduler) executeQueuedStorage(j ffs.StorageJob) { } s.notifier.RegisterJob(j.ID, a.Cfg.Notifications) + s.notifier.Alert(notifications.DiskSpaceAlert{JobID: j.ID}, a.Cfg.Notifications) // Execute s.l.Log(ctx, "Executing job %s...", j.ID) @@ -646,6 +647,7 @@ func (s *Scheduler) executeQueuedRetrievals(j ffs.RetrievalJob) { } s.notifier.RegisterJob(j.ID, a.Notifications) + s.notifier.Alert(notifications.DiskSpaceAlert{JobID: j.ID}, a.Notifications) // Execute s.l.Log(ctx, "Executing job %s...", j.ID) diff --git a/ffs/scheduler/scheduler_storage.go b/ffs/scheduler/scheduler_storage.go index 265b14a66..8a13380db 100644 --- a/ffs/scheduler/scheduler_storage.go +++ b/ffs/scheduler/scheduler_storage.go @@ -13,6 +13,7 @@ import ( "github.com/textileio/powergate/v2/ffs/scheduler/internal/astore" "github.com/textileio/powergate/v2/ffs/scheduler/internal/cistore" "github.com/textileio/powergate/v2/ffs/scheduler/internal/sjstore" + "github.com/textileio/powergate/v2/notifications" ) // PushConfig queues the specified StorageConfig to be executed as a new Job. It returns @@ -83,6 +84,7 @@ func (s *Scheduler) push(iid ffs.APIID, c cid.Cid, cfg ffs.StorageConfig, oldCid s.l.Log(ctx, "Configuration saved successfully") s.notifier.RegisterJob(j.ID, cfg.Notifications) + s.notifier.Alert(notifications.DiskSpaceAlert{JobID: jid}, cfg.Notifications) return jid, nil } diff --git a/notifications/alerts.go b/notifications/alerts.go new file mode 100644 index 000000000..e69310b77 --- /dev/null +++ b/notifications/alerts.go @@ -0,0 +1,105 @@ +package notifications + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "os" + + "github.com/docker/go-units" + "github.com/textileio/powergate/v2/ffs" + "golang.org/x/sys/unix" +) + +const ( + DiskSpaceCheck = "datacap" +) + +type DiskSpaceAlert struct { + JobID ffs.JobID +} + +type diskSpaceAlertNotification struct { + JobID ffs.JobID `json:"jobId"` + AlertType string `json:"alertType"` + AvailableDiskSpace string `json:"availableDiskSpace"` + Error string `json:"error"` +} + +func (d DiskSpaceAlert) Payload() (io.Reader, error) { + availableDiskSpace, err := getAvailableDiskSpace() + if err != nil { + err = fmt.Errorf("failed to get available disk space: %w", err) + log.Error(err) + return nil, err + } + + obj := &diskSpaceAlertNotification{ + JobID: d.JobID, + AlertType: DiskSpaceCheck, + AvailableDiskSpace: units.BytesSize(float64(availableDiskSpace)), + Error: "available disk space below threshold", + } + + data, err := json.Marshal(obj) + if err != nil { + return nil, err + } + + return bytes.NewBuffer(data), nil +} + +func (d DiskSpaceAlert) MatchEvent(event string) bool { + return false +} + +func (d DiskSpaceAlert) MatchAlert(alert *ffs.WebhookAlert) bool { + if alert == nil { + return false + } + + if alert.Type != DiskSpaceCheck { + return false + } + + threshold, err := parseDiskSpaceThresholdToBytes(alert.Threshold) + if err != nil { + log.Errorf("failed to parse alert disk space threshold: %s", err) + return false + } + + availableDiskSpace, err := getAvailableDiskSpace() + if err != nil { + log.Errorf("failed to get available disk space: %s", err) + return false + } + + return availableDiskSpace < threshold +} + +func parseDiskSpaceThresholdToBytes(threshold string) (uint64, error) { + size, err := units.RAMInBytes(threshold) + if err != nil { + return 0, err + } + + return uint64(size), nil +} + +// getAvailableDiskSpace - provides available disk space in bytes +func getAvailableDiskSpace() (uint64, error) { + var stat unix.Statfs_t + + wd, err := os.Getwd() + if err != nil { + return 0, err + } + + if err := unix.Statfs(wd, &stat); err != nil { + return 0, err + } + + // available blocks * size per block + return stat.Bavail * uint64(stat.Bsize), nil +} diff --git a/notifications/events.go b/notifications/events.go index 41a370823..7a718dfbc 100644 --- a/notifications/events.go +++ b/notifications/events.go @@ -1,18 +1,17 @@ package notifications const ( - all = "*" - created = "created" - completed = "completed" - retried = "retried" - failed = "failed" - canceled = "canceled" - expired = "expired" - slashed = "slashed" - separator = "-" - storageDeal = "storage-deal" - storageAuction = "storage-auction" - dataRetrieval = "data-retrieval" + all = "*" + created = "created" + completed = "completed" + retried = "retried" + failed = "failed" + canceled = "canceled" + expired = "expired" + slashed = "slashed" + separator = "-" + storageDeal = "storage-deal" + dataRetrieval = "data-retrieval" // All events AllEvents = all @@ -32,13 +31,6 @@ const ( StorageDealExpiredEvent = storageDeal + separator + expired StorageDealSlashedEvent = storageDeal + separator + slashed - // Storage auction events - AllStorageAuctionEvents = storageAuction + separator + all - StorageAuctionCreatedEvent = storageAuction + separator + created - StorageAuctionCompletedEvent = storageAuction + separator + completed - StorageAuctionFailedEvent = storageAuction + separator + failed - StorageAuctionCanceledEvent = storageAuction + separator + canceled - // Data retrieval events AllDataRetrievalEvents = dataRetrieval + separator + all DataRetrievalCreatedEvent = dataRetrieval + separator + created diff --git a/notifications/final_job_status.go b/notifications/final_job_status.go index b3f441cf7..4af70c284 100644 --- a/notifications/final_job_status.go +++ b/notifications/final_job_status.go @@ -23,11 +23,11 @@ func (f FinalJobStatus) FinalUpdates() bool { return true } -func (f FinalJobStatus) MatchNotificationAlert(alert *ffs.WebhookAlert) bool { +func (f FinalJobStatus) MatchAlert(alert *ffs.WebhookAlert) bool { return false } -func (f FinalJobStatus) MatchNotificationEvent(event string) bool { +func (f FinalJobStatus) MatchEvent(event string) bool { switch event { case AllEvents, AllStorageDealEvents: return true diff --git a/notifications/job_updates.go b/notifications/job_updates.go index 37dd1b450..cadebb757 100644 --- a/notifications/job_updates.go +++ b/notifications/job_updates.go @@ -10,7 +10,11 @@ type JobUpdates interface { JobID() ffs.JobID FinalUpdates() bool + Notification +} + +type Notification interface { Payload() (io.Reader, error) - MatchNotificationEvent(event string) bool - MatchNotificationAlert(alert *ffs.WebhookAlert) bool + MatchEvent(event string) bool + MatchAlert(alert *ffs.WebhookAlert) bool } diff --git a/notifications/notifier.go b/notifications/notifier.go index a0673462e..ddd775e59 100644 --- a/notifications/notifier.go +++ b/notifications/notifier.go @@ -16,6 +16,7 @@ var ( type Notifier interface { RegisterJob(jobId ffs.JobID, configs []*ffs.NotificationConfig) NotifyJobUpdates(job JobUpdates) + Alert(alert Notification, configs []*ffs.NotificationConfig) } type notifier struct { @@ -24,6 +25,7 @@ type notifier struct { updates chan JobUpdates toDelete chan ffs.JobID notifications chan *notification + alerts chan *alert } func New(ctx context.Context) *notifier { @@ -33,6 +35,7 @@ func New(ctx context.Context) *notifier { updates: make(chan JobUpdates, 1000), toDelete: make(chan ffs.JobID, 1000), notifications: make(chan *notification, 1000), + alerts: make(chan *alert, 1000), } go nt.run() @@ -45,6 +48,22 @@ func New(ctx context.Context) *notifier { return nt } +type alert struct { + notification Notification + configs []*ffs.NotificationConfig +} + +func (n *notifier) Alert(notification Notification, configs []*ffs.NotificationConfig) { + if configs == nil { + return + } + + n.alerts <- &alert{ + notification: notification, + configs: configs, + } +} + func (n *notifier) RegisterJob(jobId ffs.JobID, configs []*ffs.NotificationConfig) { if configs == nil { return @@ -78,23 +97,30 @@ func (n *notifier) run() { if updates.FinalUpdates() { n.configs.delete(updates.JobID()) } + + case alert, ok := <-n.alerts: + if !ok { + return + } + + n.notifyAll(alert.configs, alert.notification) } } } -func (n *notifier) notifyAll(configs []*ffs.NotificationConfig, updates JobUpdates) { +func (n *notifier) notifyAll(configs []*ffs.NotificationConfig, notification Notification) { for _, cfg := range configs { if cfg == nil { continue } - n.notify(cfg, updates) + n.notify(cfg, notification) } } -func (n *notifier) notify(config *ffs.NotificationConfig, updates JobUpdates) { - if matchNotificationConfig(config.Configuration, updates) { - payload, err := updates.Payload() +func (n *notifier) notify(config *ffs.NotificationConfig, notification Notification) { + if matchEventsOrAlerts(config.Configuration, notification) { + payload, err := notification.Payload() if err != nil { log.Errorf("failed to make notification payload: %s", err) return @@ -104,17 +130,17 @@ func (n *notifier) notify(config *ffs.NotificationConfig, updates JobUpdates) { } } -func matchNotificationConfig(config *ffs.WebhookConfiguration, updates JobUpdates) bool { +func matchEventsOrAlerts(config *ffs.WebhookConfiguration, notification Notification) bool { if config == nil { return false } - return matchNotificationEvents(config.Events, updates) || matchNotificationAlerts(config.Alerts, updates) + return matchEvents(config.Events, notification) || matchAlerts(config.Alerts, notification) } -func matchNotificationEvents(events []string, updates JobUpdates) bool { +func matchEvents(events []string, notification Notification) bool { for _, event := range events { - if updates.MatchNotificationEvent(event) { + if notification.MatchEvent(event) { return true } } @@ -122,9 +148,9 @@ func matchNotificationEvents(events []string, updates JobUpdates) bool { return false } -func matchNotificationAlerts(alerts []*ffs.WebhookAlert, updates JobUpdates) bool { +func matchAlerts(alerts []*ffs.WebhookAlert, notification Notification) bool { for _, alert := range alerts { - if updates.MatchNotificationAlert(alert) { + if notification.MatchAlert(alert) { return true } } diff --git a/notifications/retrieval_job.go b/notifications/retrieval_job.go index a0848373c..0f9dda600 100644 --- a/notifications/retrieval_job.go +++ b/notifications/retrieval_job.go @@ -55,7 +55,7 @@ func (r RetrievalJobUpdates) Payload() (io.Reader, error) { return bytes.NewBuffer(data), nil } -func (r RetrievalJobUpdates) MatchNotificationEvent(event string) bool { +func (r RetrievalJobUpdates) MatchEvent(event string) bool { switch event { case AllEvents, AllDataRetrievalEvents: return true @@ -79,7 +79,6 @@ func (r RetrievalJobUpdates) MatchNotificationEvent(event string) bool { } } -func (r RetrievalJobUpdates) MatchNotificationAlert(alert *ffs.WebhookAlert) bool { - // TODO +func (r RetrievalJobUpdates) MatchAlert(alert *ffs.WebhookAlert) bool { return false } diff --git a/notifications/storage_job.go b/notifications/storage_job.go index 0fb33ceb4..3982d5ad2 100644 --- a/notifications/storage_job.go +++ b/notifications/storage_job.go @@ -23,12 +23,11 @@ func (s StorageJobUpdates) FinalUpdates() bool { return false } -func (s StorageJobUpdates) MatchNotificationAlert(alert *ffs.WebhookAlert) bool { - // TODO: +func (s StorageJobUpdates) MatchAlert(alert *ffs.WebhookAlert) bool { return false } -func (s StorageJobUpdates) MatchNotificationEvent(event string) bool { +func (s StorageJobUpdates) MatchEvent(event string) bool { switch event { case AllEvents, AllStorageDealEvents: return true From 570b79db869efa905894cfabd995e3183b00d369 Mon Sep 17 00:00:00 2001 From: Volodymyr Manilo Date: Tue, 28 Jun 2022 00:10:25 +0300 Subject: [PATCH 6/7] added comments to notifier --- notifications/alerts.go | 6 ++++++ notifications/events.go | 2 ++ notifications/final_job_status.go | 6 ++++++ notifications/job_updates.go | 7 +++++++ notifications/notifier.go | 4 ++++ notifications/retrieval_job.go | 1 + notifications/storage_job.go | 1 + notifications/store.go | 2 ++ 8 files changed, 29 insertions(+) diff --git a/notifications/alerts.go b/notifications/alerts.go index e69310b77..6d2e2f61e 100644 --- a/notifications/alerts.go +++ b/notifications/alerts.go @@ -12,10 +12,13 @@ import ( "golang.org/x/sys/unix" ) +// Alert types + const ( DiskSpaceCheck = "datacap" ) +// DiskSpaceAlert - uses for alert notification when available disk space goes below threshold type DiskSpaceAlert struct { JobID ffs.JobID } @@ -27,6 +30,7 @@ type diskSpaceAlertNotification struct { Error string `json:"error"` } +// Payload - forms notification payload func (d DiskSpaceAlert) Payload() (io.Reader, error) { availableDiskSpace, err := getAvailableDiskSpace() if err != nil { @@ -50,10 +54,12 @@ func (d DiskSpaceAlert) Payload() (io.Reader, error) { return bytes.NewBuffer(data), nil } +// MatchEvent - checks for matching with events func (d DiskSpaceAlert) MatchEvent(event string) bool { return false } +// MatchAlert - checks for matching with alerts func (d DiskSpaceAlert) MatchAlert(alert *ffs.WebhookAlert) bool { if alert == nil { return false diff --git a/notifications/events.go b/notifications/events.go index 7a718dfbc..4e8ec07e7 100644 --- a/notifications/events.go +++ b/notifications/events.go @@ -1,5 +1,7 @@ package notifications +// Event types + const ( all = "*" created = "created" diff --git a/notifications/final_job_status.go b/notifications/final_job_status.go index 4af70c284..cec702b04 100644 --- a/notifications/final_job_status.go +++ b/notifications/final_job_status.go @@ -8,6 +8,7 @@ import ( "github.com/textileio/powergate/v2/ffs" ) +// FinalJobStatus - uses for notification about final job status type FinalJobStatus struct { JobId ffs.JobID JobStatus ffs.JobStatus @@ -15,18 +16,22 @@ type FinalJobStatus struct { DealErrors []ffs.DealError } +// JobID - expose job id func (f FinalJobStatus) JobID() ffs.JobID { return f.JobId } +// FinalUpdates - checks if it's final job update func (f FinalJobStatus) FinalUpdates() bool { return true } +// MatchAlert - matches for alerts func (f FinalJobStatus) MatchAlert(alert *ffs.WebhookAlert) bool { return false } +// MatchEvent - matches for events func (f FinalJobStatus) MatchEvent(event string) bool { switch event { case AllEvents, AllStorageDealEvents: @@ -59,6 +64,7 @@ type dealError struct { Message string `json:"error"` } +// Payload - forms notification payload for final job update func (f FinalJobStatus) Payload() (io.Reader, error) { var errMessage string if f.JobError != nil { diff --git a/notifications/job_updates.go b/notifications/job_updates.go index cadebb757..f8b8180bb 100644 --- a/notifications/job_updates.go +++ b/notifications/job_updates.go @@ -6,15 +6,22 @@ import ( "github.com/textileio/powergate/v2/ffs" ) +// JobUpdates - provides interface for job notifications type JobUpdates interface { + // JobID - expose job id JobID() ffs.JobID + // FinalUpdates - indicates if it's a final job update FinalUpdates() bool Notification } +// Notification - common interface for notifications (events and alerts) type Notification interface { + // Payload - forms notification payload Payload() (io.Reader, error) + // MatchEvent - matches notification by events MatchEvent(event string) bool + // MatchAlert - matches notification by alerts MatchAlert(alert *ffs.WebhookAlert) bool } diff --git a/notifications/notifier.go b/notifications/notifier.go index ddd775e59..debd94b35 100644 --- a/notifications/notifier.go +++ b/notifications/notifier.go @@ -13,9 +13,13 @@ var ( log = logging.Logger("notifier") ) +// Notifier - notifies external systems about job updates by subscribing to events and alerts type Notifier interface { + // RegisterJob - stores notification configuration for each job RegisterJob(jobId ffs.JobID, configs []*ffs.NotificationConfig) + // NotifyJobUpdates - notifies about job updates according to notification configurations NotifyJobUpdates(job JobUpdates) + // Alert - triggers check and notifies in case of alert Alert(alert Notification, configs []*ffs.NotificationConfig) } diff --git a/notifications/retrieval_job.go b/notifications/retrieval_job.go index 0f9dda600..bb418b5ba 100644 --- a/notifications/retrieval_job.go +++ b/notifications/retrieval_job.go @@ -9,6 +9,7 @@ import ( "github.com/textileio/powergate/v2/ffs" ) +// RetrievalJobUpdates - uses for retrieval job events type RetrievalJobUpdates struct { Job ffs.RetrievalJob Info ffs.RetrievalInfo diff --git a/notifications/storage_job.go b/notifications/storage_job.go index 3982d5ad2..a62c97dcd 100644 --- a/notifications/storage_job.go +++ b/notifications/storage_job.go @@ -10,6 +10,7 @@ import ( "github.com/textileio/powergate/v2/ffs" ) +// StorageJobUpdates - uses for retrieval job events type StorageJobUpdates struct { Job ffs.StorageJob Info deals.StorageDealInfo diff --git a/notifications/store.go b/notifications/store.go index 78e30d322..8ad2ab928 100644 --- a/notifications/store.go +++ b/notifications/store.go @@ -6,6 +6,8 @@ import ( "github.com/textileio/powergate/v2/ffs" ) +// in-memory store for job's notification configs + type configStore struct { sync.RWMutex From e1e5fd45c435d256a0c260779d196fba92951579 Mon Sep 17 00:00:00 2001 From: Volodymyr Manilo Date: Wed, 20 Jul 2022 22:39:01 +0200 Subject: [PATCH 7/7] added alert for deals pending expiration --- ffs/filcold/filcold.go | 10 +++ ffs/interfaces.go | 3 + ffs/manager/manager.go | 4 + ffs/scheduler/scheduler_storage.go | 40 +++++++-- notifications/alerts.go | 107 +------------------------ notifications/deal_expiration_alert.go | 92 +++++++++++++++++++++ notifications/disk_space_alert.go | 105 ++++++++++++++++++++++++ notifications/retrieval_job.go | 5 +- tests/txmapds.go | 5 +- 9 files changed, 254 insertions(+), 117 deletions(-) create mode 100644 notifications/deal_expiration_alert.go create mode 100644 notifications/disk_space_alert.go diff --git a/ffs/filcold/filcold.go b/ffs/filcold/filcold.go index 512749bbe..ade56fd0b 100644 --- a/ffs/filcold/filcold.go +++ b/ffs/filcold/filcold.go @@ -223,6 +223,16 @@ func (fc *FilCold) GetDealInfo(ctx context.Context, dealID uint64) (api.MarketDe return di, nil } +// GetCurrentEpoch returns on-chain current epoch +func (fc *FilCold) GetCurrentEpoch(ctx context.Context) (uint64, error) { + currentEpoch, err := fc.chain.GetHeight(ctx) + if err != nil { + return 0, fmt.Errorf("get current filecoin epoch: %s", err) + } + + return currentEpoch, nil +} + // EnsureRenewals analyzes a FilInfo state for a Cid and executes renewals considering the FilConfig desired configuration. // Deal status updates are sent on the provided dealUpdates channel. // The caller should close the channel once all calls to EnsureRenewals have returned. diff --git a/ffs/interfaces.go b/ffs/interfaces.go index 8dbeb5928..986300bd7 100644 --- a/ffs/interfaces.go +++ b/ffs/interfaces.go @@ -122,6 +122,9 @@ type ColdStorage interface { // If a deal ID doesn't exist, the deal isn't active anymore, or was // slashed, then ErrOnChainDealNotFound is returned. GetDealInfo(context.Context, uint64) (api.MarketDeal, error) + + // GetCurrentEpoch returns information about current epoch on-chain. + GetCurrentEpoch(context.Context) (uint64, error) } // MinerSelector returns miner addresses and ask storage information using a diff --git a/ffs/manager/manager.go b/ffs/manager/manager.go index e31b182e9..39b133daa 100644 --- a/ffs/manager/manager.go +++ b/ffs/manager/manager.go @@ -77,6 +77,10 @@ var ( Type: "datacap", Threshold: "500 GB", }, + { + Type: "storage-deal-pending-expiration", + Threshold: "1000h", + }, }, }, }, diff --git a/ffs/scheduler/scheduler_storage.go b/ffs/scheduler/scheduler_storage.go index 8a13380db..9a821b636 100644 --- a/ffs/scheduler/scheduler_storage.go +++ b/ffs/scheduler/scheduler_storage.go @@ -246,7 +246,7 @@ func (s *Scheduler) executeStorage(ctx context.Context, a astore.StorageAction, } s.l.Log(ctx, "Executing Cold-Storage configuration...") - cold, errors, err := s.executeColdStorage(ctx, ci, a.Cfg.Cold, dealUpdates) + cold, errors, err := s.executeColdStorage(ctx, ci, a.Cfg, dealUpdates) if err != nil { s.l.Log(ctx, "Cold-Storage execution failed.") return ffs.StorageInfo{}, errors, fmt.Errorf("executing cold-storage config: %s", err) @@ -397,8 +397,8 @@ func (s *Scheduler) getRefreshedColdInfo(ctx context.Context, curr ffs.ColdInfo) return curr, nil } -func (s *Scheduler) executeColdStorage(ctx context.Context, curr ffs.StorageInfo, cfg ffs.ColdConfig, dealUpdates chan deals.StorageDealInfo) (ffs.ColdInfo, []ffs.DealError, error) { - if !cfg.Enabled { +func (s *Scheduler) executeColdStorage(ctx context.Context, curr ffs.StorageInfo, cfg ffs.StorageConfig, dealUpdates chan deals.StorageDealInfo) (ffs.ColdInfo, []ffs.DealError, error) { + if !cfg.Cold.Enabled { s.l.Log(ctx, "Cold-Storage was disabled, Filecoin deals will eventually expire.") return curr.Cold, nil, nil } @@ -426,12 +426,36 @@ func (s *Scheduler) executeColdStorage(ctx context.Context, curr ffs.StorageInfo } } + currentEpoch, err := s.cs.GetCurrentEpoch(ctx) + if err != nil { + log.Error(err) + } else { + for _, deal := range curr.Cold.Filecoin.Proposals { + // no need to alert for renewed deals + if deal.Renewed { + continue + } + + s.notifier.Alert( + notifications.DealExpirationAlert{ + JobID: curr.JobID, + DealID: deal.DealID, + PieceCid: deal.PieceCid, + Miner: deal.Miner, + ExpiryEpoch: deal.StartEpoch + uint64(deal.Duration), + CurrentEpoch: currentEpoch, + }, + cfg.Notifications, + ) + } + } + // 2. If this Storage Config is renewable, then let's check if any of the existing deals // should be renewed, and do it. - if cfg.Filecoin.Renew.Enabled { + if cfg.Cold.Filecoin.Renew.Enabled { if curr.Hot.Enabled { s.l.Log(ctx, "Checking deal renewals...") - newFilInfo, errors, err := s.cs.EnsureRenewals(ctx, curr.Cid, curr.Cold.Filecoin, cfg.Filecoin, s.dealFinalityTimeout, dealUpdates) + newFilInfo, errors, err := s.cs.EnsureRenewals(ctx, curr.Cid, curr.Cold.Filecoin, cfg.Cold.Filecoin, s.dealFinalityTimeout, dealUpdates) if err != nil { s.l.Log(ctx, "Deal renewal process couldn't be executed: %s", err) } else { @@ -476,18 +500,18 @@ func (s *Scheduler) executeColdStorage(ctx context.Context, curr ffs.StorageInfo // Do we need to do some work? if s.sr2RepFactor != nil { - cfg.Filecoin.RepFactor, err = s.sr2RepFactor() + cfg.Cold.Filecoin.RepFactor, err = s.sr2RepFactor() if err != nil { return ffs.ColdInfo{}, nil, fmt.Errorf("getting SR2 replication factor: %s", err) } } - if cfg.Filecoin.RepFactor-len(curr.Cold.Filecoin.Proposals) <= 0 { + if cfg.Cold.Filecoin.RepFactor-len(curr.Cold.Filecoin.Proposals) <= 0 { s.l.Log(ctx, "The current replication factor is equal or higher than desired, avoiding making new deals.") return curr.Cold, nil, nil } // The answer is yes, calculate how many extra deals we need and create them. - deltaFilConfig := createDeltaFilConfig(cfg, curr.Cold.Filecoin) + deltaFilConfig := createDeltaFilConfig(cfg.Cold, curr.Cold.Filecoin) s.l.Log(ctx, "Current replication factor is lower than desired, making %d new deals...", deltaFilConfig.RepFactor) startedProposals, rejectedProposals, size, err := s.cs.Store(ctx, curr.Cid, deltaFilConfig) if err != nil { diff --git a/notifications/alerts.go b/notifications/alerts.go index 6d2e2f61e..652921038 100644 --- a/notifications/alerts.go +++ b/notifications/alerts.go @@ -1,111 +1,8 @@ package notifications -import ( - "bytes" - "encoding/json" - "fmt" - "io" - "os" - - "github.com/docker/go-units" - "github.com/textileio/powergate/v2/ffs" - "golang.org/x/sys/unix" -) - // Alert types const ( - DiskSpaceCheck = "datacap" + DiskSpaceCheck = "datacap" + DealExpirationCheck = "storage-deal-pending-expiration" ) - -// DiskSpaceAlert - uses for alert notification when available disk space goes below threshold -type DiskSpaceAlert struct { - JobID ffs.JobID -} - -type diskSpaceAlertNotification struct { - JobID ffs.JobID `json:"jobId"` - AlertType string `json:"alertType"` - AvailableDiskSpace string `json:"availableDiskSpace"` - Error string `json:"error"` -} - -// Payload - forms notification payload -func (d DiskSpaceAlert) Payload() (io.Reader, error) { - availableDiskSpace, err := getAvailableDiskSpace() - if err != nil { - err = fmt.Errorf("failed to get available disk space: %w", err) - log.Error(err) - return nil, err - } - - obj := &diskSpaceAlertNotification{ - JobID: d.JobID, - AlertType: DiskSpaceCheck, - AvailableDiskSpace: units.BytesSize(float64(availableDiskSpace)), - Error: "available disk space below threshold", - } - - data, err := json.Marshal(obj) - if err != nil { - return nil, err - } - - return bytes.NewBuffer(data), nil -} - -// MatchEvent - checks for matching with events -func (d DiskSpaceAlert) MatchEvent(event string) bool { - return false -} - -// MatchAlert - checks for matching with alerts -func (d DiskSpaceAlert) MatchAlert(alert *ffs.WebhookAlert) bool { - if alert == nil { - return false - } - - if alert.Type != DiskSpaceCheck { - return false - } - - threshold, err := parseDiskSpaceThresholdToBytes(alert.Threshold) - if err != nil { - log.Errorf("failed to parse alert disk space threshold: %s", err) - return false - } - - availableDiskSpace, err := getAvailableDiskSpace() - if err != nil { - log.Errorf("failed to get available disk space: %s", err) - return false - } - - return availableDiskSpace < threshold -} - -func parseDiskSpaceThresholdToBytes(threshold string) (uint64, error) { - size, err := units.RAMInBytes(threshold) - if err != nil { - return 0, err - } - - return uint64(size), nil -} - -// getAvailableDiskSpace - provides available disk space in bytes -func getAvailableDiskSpace() (uint64, error) { - var stat unix.Statfs_t - - wd, err := os.Getwd() - if err != nil { - return 0, err - } - - if err := unix.Statfs(wd, &stat); err != nil { - return 0, err - } - - // available blocks * size per block - return stat.Bavail * uint64(stat.Bsize), nil -} diff --git a/notifications/deal_expiration_alert.go b/notifications/deal_expiration_alert.go new file mode 100644 index 000000000..484daa6d0 --- /dev/null +++ b/notifications/deal_expiration_alert.go @@ -0,0 +1,92 @@ +package notifications + +import ( + "bytes" + "encoding/json" + "io" + "time" + + "github.com/ipfs/go-cid" + "github.com/textileio/powergate/v2/ffs" + "github.com/textileio/powergate/v2/util" +) + +// DealExpirationAlert - uses for alert notification when deal close to expiration and goes below threshold +type DealExpirationAlert struct { + JobID ffs.JobID + DealID uint64 + PieceCid cid.Cid + Miner string + ExpiryEpoch uint64 + CurrentEpoch uint64 +} + +type dealExpirationAlertNotification struct { + AlertType string `json:"alertType"` + JobID ffs.JobID `json:"jobId"` + DealID uint64 `json:"dealId"` + PieceCid string `json:"pieceCid"` + Miner string `json:"miner"` + EpochTillExpiration uint64 `json:"epochTillExpiration"` + Error string `json:"error"` +} + +// Payload - forms notification payload +func (d DealExpirationAlert) Payload() (io.Reader, error) { + var epochTillExpiration uint64 + msg := "deal already expired" + + if d.ExpiryEpoch > d.CurrentEpoch { + msg = "deal close to expiration" + epochTillExpiration = d.ExpiryEpoch - d.CurrentEpoch + } + + obj := &dealExpirationAlertNotification{ + AlertType: DealExpirationCheck, + JobID: d.JobID, + DealID: d.DealID, + PieceCid: d.PieceCid.String(), + Miner: d.Miner, + EpochTillExpiration: epochTillExpiration, + Error: msg, + } + + data, err := json.Marshal(obj) + if err != nil { + return nil, err + } + + return bytes.NewBuffer(data), nil +} + +// MatchEvent - checks for matching with events +func (d DealExpirationAlert) MatchEvent(event string) bool { + return false +} + +// MatchAlert - checks for matching with alerts +func (d DealExpirationAlert) MatchAlert(alert *ffs.WebhookAlert) bool { + if alert == nil { + return false + } + + if alert.Type != DealExpirationCheck { + return false + } + + threshold, err := time.ParseDuration(alert.Threshold) + if err != nil { + log.Errorf("failed to parse deal expiration check threshold: %s", err) + return false + } + + if d.ExpiryEpoch <= d.CurrentEpoch { + // already expired + return true + } + + epochTillExpiration := d.ExpiryEpoch - d.CurrentEpoch + epochThreshold := uint64(threshold.Seconds() / util.EpochDurationSeconds) + + return epochTillExpiration < epochThreshold +} diff --git a/notifications/disk_space_alert.go b/notifications/disk_space_alert.go new file mode 100644 index 000000000..3592c0ac6 --- /dev/null +++ b/notifications/disk_space_alert.go @@ -0,0 +1,105 @@ +package notifications + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "os" + + "github.com/docker/go-units" + "github.com/textileio/powergate/v2/ffs" + "golang.org/x/sys/unix" +) + +// DiskSpaceAlert - uses for alert notification when available disk space goes below threshold +type DiskSpaceAlert struct { + JobID ffs.JobID +} + +type diskSpaceAlertNotification struct { + JobID ffs.JobID `json:"jobId"` + AlertType string `json:"alertType"` + AvailableDiskSpace string `json:"availableDiskSpace"` + Error string `json:"error"` +} + +// Payload - forms notification payload +func (d DiskSpaceAlert) Payload() (io.Reader, error) { + availableDiskSpace, err := getAvailableDiskSpace() + if err != nil { + err = fmt.Errorf("failed to get available disk space: %w", err) + log.Error(err) + return nil, err + } + + obj := &diskSpaceAlertNotification{ + JobID: d.JobID, + AlertType: DiskSpaceCheck, + AvailableDiskSpace: units.BytesSize(float64(availableDiskSpace)), + Error: "available disk space below threshold", + } + + data, err := json.Marshal(obj) + if err != nil { + return nil, err + } + + return bytes.NewBuffer(data), nil +} + +// MatchEvent - checks for matching with events +func (d DiskSpaceAlert) MatchEvent(event string) bool { + return false +} + +// MatchAlert - checks for matching with alerts +func (d DiskSpaceAlert) MatchAlert(alert *ffs.WebhookAlert) bool { + if alert == nil { + return false + } + + if alert.Type != DiskSpaceCheck { + return false + } + + threshold, err := parseDiskSpaceThresholdToBytes(alert.Threshold) + if err != nil { + log.Errorf("failed to parse alert disk space threshold: %s", err) + return false + } + + availableDiskSpace, err := getAvailableDiskSpace() + if err != nil { + log.Errorf("failed to get available disk space: %s", err) + return false + } + + return availableDiskSpace < threshold +} + +func parseDiskSpaceThresholdToBytes(threshold string) (uint64, error) { + size, err := units.RAMInBytes(threshold) + if err != nil { + return 0, err + } + + return uint64(size), nil +} + +// getAvailableDiskSpace - provides available disk space in bytes +func getAvailableDiskSpace() (uint64, error) { + var stat unix.Statfs_t + + wd, err := os.Getwd() + if err != nil { + return 0, err + } + + if err := unix.Statfs(wd, &stat); err != nil { + return 0, err + } + + // available blocks * size per block + return stat.Bavail * uint64(stat.Bsize), nil +} diff --git a/notifications/retrieval_job.go b/notifications/retrieval_job.go index bb418b5ba..2a031cb59 100644 --- a/notifications/retrieval_job.go +++ b/notifications/retrieval_job.go @@ -31,8 +31,9 @@ type retrievalJobNotification struct { TotalPaid uint64 `json:"totalPaid"` Miner string `json:"miner"` Size int64 `json:"size"` - CreatedAt string `json:"createdAt"` - ErrCause string `json:"error,omitempty"` + // CreatedAt timestamp in RFC3339 format + CreatedAt string `json:"createdAt"` + ErrCause string `json:"error,omitempty"` } func (r RetrievalJobUpdates) Payload() (io.Reader, error) { diff --git a/tests/txmapds.go b/tests/txmapds.go index ed82d3916..150cfdb23 100644 --- a/tests/txmapds.go +++ b/tests/txmapds.go @@ -181,5 +181,6 @@ func NewMockNotifier() *mockNotifier { return &mockNotifier{} } -func (n *mockNotifier) RegisterJob(jobId ffs.JobID, configs []*ffs.NotificationConfig) {} -func (n *mockNotifier) NotifyJobUpdates(job notifications.JobUpdates) {} +func (n *mockNotifier) RegisterJob(jobId ffs.JobID, configs []*ffs.NotificationConfig) {} +func (n *mockNotifier) NotifyJobUpdates(job notifications.JobUpdates) {} +func (n *mockNotifier) Alert(alert notifications.Notification, configs []*ffs.NotificationConfig) {}