From 22f23b735e13ba4116611b6033d88beee33ea592 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Mon, 7 Oct 2024 14:22:40 -0400 Subject: [PATCH 1/3] Allow different justification types --- proto/pb/platformvm/platformvm.pb.go | 282 +++++++++++++++++++++++++++ proto/platformvm/platformvm.proto | 21 ++ vms/platformvm/network/warp.go | 135 ++++++++++--- 3 files changed, 412 insertions(+), 26 deletions(-) create mode 100644 proto/pb/platformvm/platformvm.pb.go create mode 100644 proto/platformvm/platformvm.proto diff --git a/proto/pb/platformvm/platformvm.pb.go b/proto/pb/platformvm/platformvm.pb.go new file mode 100644 index 000000000000..10b40671a881 --- /dev/null +++ b/proto/pb/platformvm/platformvm.pb.go @@ -0,0 +1,282 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.33.0 +// protoc (unknown) +// source: platformvm/platformvm.proto + +package platformvm + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type SubnetValidatorRegistrationJustification struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Types that are assignable to Preimage: + // + // *SubnetValidatorRegistrationJustification_ConvertSubnetTxData + // *SubnetValidatorRegistrationJustification_RegisterSubnetValidatorMessage + Preimage isSubnetValidatorRegistrationJustification_Preimage `protobuf_oneof:"preimage"` + Filter []byte `protobuf:"bytes,3,opt,name=filter,proto3" json:"filter,omitempty"` +} + +func (x *SubnetValidatorRegistrationJustification) Reset() { + *x = SubnetValidatorRegistrationJustification{} + if protoimpl.UnsafeEnabled { + mi := &file_platformvm_platformvm_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SubnetValidatorRegistrationJustification) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SubnetValidatorRegistrationJustification) ProtoMessage() {} + +func (x *SubnetValidatorRegistrationJustification) ProtoReflect() protoreflect.Message { + mi := &file_platformvm_platformvm_proto_msgTypes[0] + 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 SubnetValidatorRegistrationJustification.ProtoReflect.Descriptor instead. +func (*SubnetValidatorRegistrationJustification) Descriptor() ([]byte, []int) { + return file_platformvm_platformvm_proto_rawDescGZIP(), []int{0} +} + +func (m *SubnetValidatorRegistrationJustification) GetPreimage() isSubnetValidatorRegistrationJustification_Preimage { + if m != nil { + return m.Preimage + } + return nil +} + +func (x *SubnetValidatorRegistrationJustification) GetConvertSubnetTxData() *SubnetIDIndex { + if x, ok := x.GetPreimage().(*SubnetValidatorRegistrationJustification_ConvertSubnetTxData); ok { + return x.ConvertSubnetTxData + } + return nil +} + +func (x *SubnetValidatorRegistrationJustification) GetRegisterSubnetValidatorMessage() []byte { + if x, ok := x.GetPreimage().(*SubnetValidatorRegistrationJustification_RegisterSubnetValidatorMessage); ok { + return x.RegisterSubnetValidatorMessage + } + return nil +} + +func (x *SubnetValidatorRegistrationJustification) GetFilter() []byte { + if x != nil { + return x.Filter + } + return nil +} + +type isSubnetValidatorRegistrationJustification_Preimage interface { + isSubnetValidatorRegistrationJustification_Preimage() +} + +type SubnetValidatorRegistrationJustification_ConvertSubnetTxData struct { + // Validator was added to the Subnet during the ConvertSubnetTx. + ConvertSubnetTxData *SubnetIDIndex `protobuf:"bytes,1,opt,name=convert_subnet_tx_data,json=convertSubnetTxData,proto3,oneof"` +} + +type SubnetValidatorRegistrationJustification_RegisterSubnetValidatorMessage struct { + // Validator was registered to the Subnet after the ConvertSubnetTx. + // The SubnetValidator is being removed from the Subnet + RegisterSubnetValidatorMessage []byte `protobuf:"bytes,2,opt,name=register_subnet_validator_message,json=registerSubnetValidatorMessage,proto3,oneof"` +} + +func (*SubnetValidatorRegistrationJustification_ConvertSubnetTxData) isSubnetValidatorRegistrationJustification_Preimage() { +} + +func (*SubnetValidatorRegistrationJustification_RegisterSubnetValidatorMessage) isSubnetValidatorRegistrationJustification_Preimage() { +} + +type SubnetIDIndex struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + SubnetId []byte `protobuf:"bytes,1,opt,name=subnet_id,json=subnetId,proto3" json:"subnet_id,omitempty"` + Index uint32 `protobuf:"varint,2,opt,name=index,proto3" json:"index,omitempty"` +} + +func (x *SubnetIDIndex) Reset() { + *x = SubnetIDIndex{} + if protoimpl.UnsafeEnabled { + mi := &file_platformvm_platformvm_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SubnetIDIndex) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SubnetIDIndex) ProtoMessage() {} + +func (x *SubnetIDIndex) ProtoReflect() protoreflect.Message { + mi := &file_platformvm_platformvm_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SubnetIDIndex.ProtoReflect.Descriptor instead. +func (*SubnetIDIndex) Descriptor() ([]byte, []int) { + return file_platformvm_platformvm_proto_rawDescGZIP(), []int{1} +} + +func (x *SubnetIDIndex) GetSubnetId() []byte { + if x != nil { + return x.SubnetId + } + return nil +} + +func (x *SubnetIDIndex) GetIndex() uint32 { + if x != nil { + return x.Index + } + return 0 +} + +var File_platformvm_platformvm_proto protoreflect.FileDescriptor + +var file_platformvm_platformvm_proto_rawDesc = []byte{ + 0x0a, 0x1b, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x76, 0x6d, 0x2f, 0x70, 0x6c, 0x61, + 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x76, 0x6d, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0a, 0x70, + 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x76, 0x6d, 0x22, 0xed, 0x01, 0x0a, 0x28, 0x53, 0x75, + 0x62, 0x6e, 0x65, 0x74, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x67, + 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4a, 0x75, 0x73, 0x74, 0x69, 0x66, 0x69, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x50, 0x0a, 0x16, 0x63, 0x6f, 0x6e, 0x76, 0x65, 0x72, + 0x74, 0x5f, 0x73, 0x75, 0x62, 0x6e, 0x65, 0x74, 0x5f, 0x74, 0x78, 0x5f, 0x64, 0x61, 0x74, 0x61, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, + 0x6d, 0x76, 0x6d, 0x2e, 0x53, 0x75, 0x62, 0x6e, 0x65, 0x74, 0x49, 0x44, 0x49, 0x6e, 0x64, 0x65, + 0x78, 0x48, 0x00, 0x52, 0x13, 0x63, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x53, 0x75, 0x62, 0x6e, + 0x65, 0x74, 0x54, 0x78, 0x44, 0x61, 0x74, 0x61, 0x12, 0x4b, 0x0a, 0x21, 0x72, 0x65, 0x67, 0x69, + 0x73, 0x74, 0x65, 0x72, 0x5f, 0x73, 0x75, 0x62, 0x6e, 0x65, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x69, + 0x64, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x1e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x53, + 0x75, 0x62, 0x6e, 0x65, 0x74, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x4d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x42, 0x0a, 0x0a, + 0x08, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x22, 0x42, 0x0a, 0x0d, 0x53, 0x75, 0x62, + 0x6e, 0x65, 0x74, 0x49, 0x44, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x75, + 0x62, 0x6e, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x73, + 0x75, 0x62, 0x6e, 0x65, 0x74, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x42, 0x35, 0x5a, + 0x33, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x76, 0x61, 0x2d, + 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x61, 0x76, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x68, 0x65, 0x67, 0x6f, + 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x70, 0x62, 0x2f, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, + 0x72, 0x6d, 0x76, 0x6d, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_platformvm_platformvm_proto_rawDescOnce sync.Once + file_platformvm_platformvm_proto_rawDescData = file_platformvm_platformvm_proto_rawDesc +) + +func file_platformvm_platformvm_proto_rawDescGZIP() []byte { + file_platformvm_platformvm_proto_rawDescOnce.Do(func() { + file_platformvm_platformvm_proto_rawDescData = protoimpl.X.CompressGZIP(file_platformvm_platformvm_proto_rawDescData) + }) + return file_platformvm_platformvm_proto_rawDescData +} + +var file_platformvm_platformvm_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_platformvm_platformvm_proto_goTypes = []interface{}{ + (*SubnetValidatorRegistrationJustification)(nil), // 0: platformvm.SubnetValidatorRegistrationJustification + (*SubnetIDIndex)(nil), // 1: platformvm.SubnetIDIndex +} +var file_platformvm_platformvm_proto_depIdxs = []int32{ + 1, // 0: platformvm.SubnetValidatorRegistrationJustification.convert_subnet_tx_data:type_name -> platformvm.SubnetIDIndex + 1, // [1:1] is the sub-list for method output_type + 1, // [1:1] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_platformvm_platformvm_proto_init() } +func file_platformvm_platformvm_proto_init() { + if File_platformvm_platformvm_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_platformvm_platformvm_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SubnetValidatorRegistrationJustification); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_platformvm_platformvm_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SubnetIDIndex); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_platformvm_platformvm_proto_msgTypes[0].OneofWrappers = []interface{}{ + (*SubnetValidatorRegistrationJustification_ConvertSubnetTxData)(nil), + (*SubnetValidatorRegistrationJustification_RegisterSubnetValidatorMessage)(nil), + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_platformvm_platformvm_proto_rawDesc, + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_platformvm_platformvm_proto_goTypes, + DependencyIndexes: file_platformvm_platformvm_proto_depIdxs, + MessageInfos: file_platformvm_platformvm_proto_msgTypes, + }.Build() + File_platformvm_platformvm_proto = out.File + file_platformvm_platformvm_proto_rawDesc = nil + file_platformvm_platformvm_proto_goTypes = nil + file_platformvm_platformvm_proto_depIdxs = nil +} diff --git a/proto/platformvm/platformvm.proto b/proto/platformvm/platformvm.proto new file mode 100644 index 000000000000..a1e9adf129d1 --- /dev/null +++ b/proto/platformvm/platformvm.proto @@ -0,0 +1,21 @@ +syntax = "proto3"; + +package platformvm; + +option go_package = "github.com/ava-labs/avalanchego/proto/pb/platformvm"; + +message SubnetValidatorRegistrationJustification { + oneof preimage { + // Validator was added to the Subnet during the ConvertSubnetTx. + SubnetIDIndex convert_subnet_tx_data = 1; + // Validator was registered to the Subnet after the ConvertSubnetTx. + // The SubnetValidator is being removed from the Subnet + bytes register_subnet_validator_message = 2; + } + bytes filter = 3; +} + +message SubnetIDIndex { + bytes subnet_id = 1; + uint32 index = 2; +} diff --git a/vms/platformvm/network/warp.go b/vms/platformvm/network/warp.go index 11e75943505f..d8aabb17f134 100644 --- a/vms/platformvm/network/warp.go +++ b/vms/platformvm/network/warp.go @@ -9,9 +9,12 @@ import ( "math" "sync" + "google.golang.org/protobuf/proto" + "github.com/ava-labs/avalanchego/database" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/network/p2p/acp118" + "github.com/ava-labs/avalanchego/proto/pb/platformvm" "github.com/ava-labs/avalanchego/snow/engine/common" "github.com/ava-labs/avalanchego/vms/platformvm/state" "github.com/ava-labs/avalanchego/vms/platformvm/warp" @@ -129,9 +132,65 @@ func (s signatureRequestVerifier) verifySubnetConversion( func (s signatureRequestVerifier) verifySubnetValidatorRegistration( msg *message.SubnetValidatorRegistration, - justification []byte, + justificationBytes []byte, +) *common.AppError { + if msg.Registered { + return s.verifySubnetValidatorRegistered(msg.ValidationID) + } + + var justification platformvm.SubnetValidatorRegistrationJustification + if err := proto.Unmarshal(justificationBytes, &justification); err != nil { + return &common.AppError{ + Code: ErrFailedToParseJustification, + Message: "failed to parse justification: " + err.Error(), + } + } + + switch preimage := justification.GetPreimage().(type) { + case *platformvm.SubnetValidatorRegistrationJustification_ConvertSubnetTxData: + return s.verifySubnetValidatorNotCurrentlyRegistered(msg.ValidationID, preimage.ConvertSubnetTxData) + case *platformvm.SubnetValidatorRegistrationJustification_RegisterSubnetValidatorMessage: + return s.verifySubnetValidatorCanNotValidate(msg.ValidationID, preimage.RegisterSubnetValidatorMessage) + default: + return &common.AppError{ + Code: ErrFailedToParseJustification, + Message: fmt.Sprintf("failed to parse justification: unsupported justification type %T", justification.Preimage), + } + } +} + +// verifySubnetValidatorCanNotValidate verifies that the validationID is +// currently a validator. +func (s signatureRequestVerifier) verifySubnetValidatorRegistered( + validationID ids.ID, +) *common.AppError { + s.stateLock.Lock() + defer s.stateLock.Unlock() + + // Verify that the validator exists + _, err := s.state.GetSubnetOnlyValidator(validationID) + if err == database.ErrNotFound { + return &common.AppError{ + Code: ErrValidationDoesNotExist, + Message: fmt.Sprintf("validation %q does not exist", validationID), + } + } + if err != nil { + return &common.AppError{ + Code: common.ErrUndefined.Code, + Message: "failed to get subnet only validator: " + err.Error(), + } + } + return nil +} + +// verifySubnetValidatorCanNotValidate verifies that the validationID is not +// currently a validator. +func (s signatureRequestVerifier) verifySubnetValidatorNotCurrentlyRegistered( + validationID ids.ID, + justification *platformvm.SubnetIDIndex, ) *common.AppError { - registerSubnetValidator, err := message.ParseRegisterSubnetValidator(justification) + subnetID, err := ids.ToID(justification.GetSubnetId()) if err != nil { return &common.AppError{ Code: ErrFailedToParseJustification, @@ -139,41 +198,65 @@ func (s signatureRequestVerifier) verifySubnetValidatorRegistration( } } - justificationID := registerSubnetValidator.ValidationID() - if msg.ValidationID != justificationID { + justificationID := subnetID.Append(justification.GetIndex()) + if validationID != justificationID { return &common.AppError{ Code: ErrMismatchedValidationID, - Message: fmt.Sprintf("validationID %q != justificationID %q", msg.ValidationID, justificationID), + Message: fmt.Sprintf("validationID %q != justificationID %q", validationID, justificationID), } } s.stateLock.Lock() defer s.stateLock.Unlock() - if msg.Registered { - // Verify that the validator exists - _, err := s.state.GetSubnetOnlyValidator(msg.ValidationID) - if err == database.ErrNotFound { - return &common.AppError{ - Code: ErrValidationDoesNotExist, - Message: fmt.Sprintf("validation %q does not exist", msg.ValidationID), - } - } - if err != nil { - return &common.AppError{ - Code: common.ErrUndefined.Code, - Message: "failed to get subnet only validator: " + err.Error(), - } + // Verify that the validator does not currently exist + _, err = s.state.GetSubnetOnlyValidator(validationID) + if err == nil { + return &common.AppError{ + Code: ErrValidationExists, + Message: fmt.Sprintf("validation %q exists", validationID), } - return nil } + if err != database.ErrNotFound { + return &common.AppError{ + Code: common.ErrUndefined.Code, + Message: "failed to lookup subnet only validator: " + err.Error(), + } + } + return nil +} + +// verifySubnetValidatorCanNotValidate verifies that the validationID does not +// currently and can never become a validator. +func (s signatureRequestVerifier) verifySubnetValidatorCanNotValidate( + validationID ids.ID, + justificationBytes []byte, +) *common.AppError { + justification, err := message.ParseRegisterSubnetValidator(justificationBytes) + if err != nil { + return &common.AppError{ + Code: ErrFailedToParseJustification, + Message: "failed to parse justification: " + err.Error(), + } + } + + justificationID := justification.ValidationID() + if validationID != justificationID { + return &common.AppError{ + Code: ErrMismatchedValidationID, + Message: fmt.Sprintf("validationID %q != justificationID %q", validationID, justificationID), + } + } + + s.stateLock.Lock() + defer s.stateLock.Unlock() // Verify that the validator does not and can never exists - _, err = s.state.GetSubnetOnlyValidator(msg.ValidationID) + _, err = s.state.GetSubnetOnlyValidator(validationID) if err == nil { return &common.AppError{ Code: ErrValidationExists, - Message: fmt.Sprintf("validation %q exists", msg.ValidationID), + Message: fmt.Sprintf("validation %q exists", validationID), } } if err != database.ErrNotFound { @@ -184,14 +267,14 @@ func (s signatureRequestVerifier) verifySubnetValidatorRegistration( } currentTimeUnix := uint64(s.state.GetTimestamp().Unix()) - if registerSubnetValidator.Expiry <= currentTimeUnix { + if justification.Expiry <= currentTimeUnix { // The validator is not registered and the expiry time has passed return nil } hasExpiry, err := s.state.HasExpiry(state.ExpiryEntry{ - Timestamp: registerSubnetValidator.Expiry, - ValidationID: msg.ValidationID, + Timestamp: justification.Expiry, + ValidationID: validationID, }) if err != nil { return &common.AppError{ @@ -202,7 +285,7 @@ func (s signatureRequestVerifier) verifySubnetValidatorRegistration( if !hasExpiry { return &common.AppError{ Code: ErrValidationCouldBeRegistered, - Message: fmt.Sprintf("validation %q can be registered until %d", msg.ValidationID, registerSubnetValidator.Expiry), + Message: fmt.Sprintf("validation %q can be registered until %d", validationID, justification.Expiry), } } From f17ff50aef8e9d25352931f4bf308ab3bfede3da Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Mon, 7 Oct 2024 14:31:16 -0400 Subject: [PATCH 2/3] nits --- vms/platformvm/network/warp.go | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/vms/platformvm/network/warp.go b/vms/platformvm/network/warp.go index d8aabb17f134..c774c37e2bde 100644 --- a/vms/platformvm/network/warp.go +++ b/vms/platformvm/network/warp.go @@ -32,9 +32,12 @@ const ( ErrConversionDoesNotExist ErrMismatchedConversionID + ErrInvalidJustificationType + ErrFailedToParseSubnetID ErrMismatchedValidationID ErrValidationDoesNotExist ErrValidationExists + ErrFailedToParseRegisterSubnetValidator ErrValidationCouldBeRegistered ErrImpossibleNonce @@ -153,8 +156,8 @@ func (s signatureRequestVerifier) verifySubnetValidatorRegistration( return s.verifySubnetValidatorCanNotValidate(msg.ValidationID, preimage.RegisterSubnetValidatorMessage) default: return &common.AppError{ - Code: ErrFailedToParseJustification, - Message: fmt.Sprintf("failed to parse justification: unsupported justification type %T", justification.Preimage), + Code: ErrInvalidJustificationType, + Message: fmt.Sprintf("invalid justification type: %T", justification.Preimage), } } } @@ -193,8 +196,8 @@ func (s signatureRequestVerifier) verifySubnetValidatorNotCurrentlyRegistered( subnetID, err := ids.ToID(justification.GetSubnetId()) if err != nil { return &common.AppError{ - Code: ErrFailedToParseJustification, - Message: "failed to parse justification: " + err.Error(), + Code: ErrFailedToParseSubnetID, + Message: "failed to parse subnetID: " + err.Error(), } } @@ -235,8 +238,8 @@ func (s signatureRequestVerifier) verifySubnetValidatorCanNotValidate( justification, err := message.ParseRegisterSubnetValidator(justificationBytes) if err != nil { return &common.AppError{ - Code: ErrFailedToParseJustification, - Message: "failed to parse justification: " + err.Error(), + Code: ErrFailedToParseRegisterSubnetValidator, + Message: "failed to parse RegisterSubnetValidator justification: " + err.Error(), } } From ea0411790cd192465532d4ba8f210285eabf3e84 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Mon, 7 Oct 2024 15:14:17 -0400 Subject: [PATCH 3/3] Disallow removal of last validator --- .../txs/executor/standard_tx_executor.go | 69 +++++++++++-------- 1 file changed, 41 insertions(+), 28 deletions(-) diff --git a/vms/platformvm/txs/executor/standard_tx_executor.go b/vms/platformvm/txs/executor/standard_tx_executor.go index f3ff2ab93557..ff4ae2dcde1e 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor.go +++ b/vms/platformvm/txs/executor/standard_tx_executor.go @@ -49,6 +49,7 @@ var ( errEtnaUpgradeNotActive = errors.New("attempting to use an Etna-upgrade feature prior to activation") errTransformSubnetTxPostEtna = errors.New("TransformSubnetTx is not permitted post-Etna") errMaxNumActiveValidators = errors.New("already at the max number of active validators") + errRemovingLastValidator = errors.New("attempting to remove the last SoV from a converted subnet") errStateCorruption = errors.New("state corruption") ) @@ -879,40 +880,52 @@ func (e *StandardTxExecutor) SetSubnetValidatorWeightTx(tx *txs.SetSubnetValidat } txID := e.Tx.ID() - if msg.Weight == 0 && sov.EndAccumulatedFee != 0 { - // If we are removing an active validator, we need to refund the - // remaining balance. - var remainingBalanceOwner message.PChainOwner - if _, err := txs.Codec.Unmarshal(sov.RemainingBalanceOwner, &remainingBalanceOwner); err != nil { + + // We are removing the validator + if msg.Weight == 0 { + weight, err := e.State.WeightOfSubnetOnlyValidators(sov.SubnetID) + if err != nil { return err } - - accruedFees := e.State.GetAccruedFees() - if sov.EndAccumulatedFee <= accruedFees { - // This check should be unreachable. However, including it ensures - // that AVAX can't get minted out of thin air due to state - // corruption. - return fmt.Errorf("%w: validator should have already been disabled", errStateCorruption) + if weight == sov.Weight { + return errRemovingLastValidator } - remainingBalance := sov.EndAccumulatedFee - accruedFees - utxo := &avax.UTXO{ - UTXOID: avax.UTXOID{ - TxID: txID, - OutputIndex: uint32(len(tx.Outs)), - }, - Asset: avax.Asset{ - ID: e.Ctx.AVAXAssetID, - }, - Out: &secp256k1fx.TransferOutput{ - Amt: remainingBalance, - OutputOwners: secp256k1fx.OutputOwners{ - Threshold: remainingBalanceOwner.Threshold, - Addrs: remainingBalanceOwner.Addresses, + // The validator is currently active, we need to refund the remaining + // balance. + if sov.EndAccumulatedFee != 0 { + var remainingBalanceOwner message.PChainOwner + if _, err := txs.Codec.Unmarshal(sov.RemainingBalanceOwner, &remainingBalanceOwner); err != nil { + return err + } + + accruedFees := e.State.GetAccruedFees() + if sov.EndAccumulatedFee <= accruedFees { + // This check should be unreachable. However, including it ensures + // that AVAX can't get minted out of thin air due to state + // corruption. + return fmt.Errorf("%w: validator should have already been disabled", errStateCorruption) + } + remainingBalance := sov.EndAccumulatedFee - accruedFees + + utxo := &avax.UTXO{ + UTXOID: avax.UTXOID{ + TxID: txID, + OutputIndex: uint32(len(tx.Outs)), }, - }, + Asset: avax.Asset{ + ID: e.Ctx.AVAXAssetID, + }, + Out: &secp256k1fx.TransferOutput{ + Amt: remainingBalance, + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: remainingBalanceOwner.Threshold, + Addrs: remainingBalanceOwner.Addresses, + }, + }, + } + e.State.AddUTXO(utxo) } - e.State.AddUTXO(utxo) } // If the weight is being set to 0, the validator is being removed and the