From 7fd21cab9bede4f1186f0aa8b5e427e15896ecbe Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 1 Oct 2024 13:24:36 -0400 Subject: [PATCH] ACP-77: Update warp messages to align with spec changes (#3430) --- vms/platformvm/warp/message/codec.go | 1 - vms/platformvm/warp/message/payload_test.go | 82 +++++---- .../warp/message/register_subnet_validator.go | 86 ++++++++-- .../message/register_subnet_validator_test.go | 161 +++++++++++++++++- .../message/set_subnet_validator_weight.go | 48 ------ .../set_subnet_validator_weight_test.go | 28 --- .../warp/message/subnet_conversion.go | 32 +++- .../warp/message/subnet_conversion_test.go | 82 +++++++++ .../warp/message/subnet_validator_weight.go | 20 ++- .../message/subnet_validator_weight_test.go | 38 +++++ 10 files changed, 447 insertions(+), 131 deletions(-) delete mode 100644 vms/platformvm/warp/message/set_subnet_validator_weight.go delete mode 100644 vms/platformvm/warp/message/set_subnet_validator_weight_test.go diff --git a/vms/platformvm/warp/message/codec.go b/vms/platformvm/warp/message/codec.go index b5ade8de30a9..4dda85a1c76e 100644 --- a/vms/platformvm/warp/message/codec.go +++ b/vms/platformvm/warp/message/codec.go @@ -23,7 +23,6 @@ func init() { lc.RegisterType(&SubnetConversion{}), lc.RegisterType(&RegisterSubnetValidator{}), lc.RegisterType(&SubnetValidatorRegistration{}), - lc.RegisterType(&SetSubnetValidatorWeight{}), lc.RegisterType(&SubnetValidatorWeight{}), Codec.RegisterCodec(CodecVersion, lc), ) diff --git a/vms/platformvm/warp/message/payload_test.go b/vms/platformvm/warp/message/payload_test.go index 802f84e01e85..dc2013f34826 100644 --- a/vms/platformvm/warp/message/payload_test.go +++ b/vms/platformvm/warp/message/payload_test.go @@ -63,21 +63,39 @@ func TestParse(t *testing.T) { 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, + // NodeID Length: + 0x00, 0x00, 0x00, 0x14, // NodeID: 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, - // Weight: - 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, // BLSPublicKey: + 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, 0x64, - 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, // Expiry: - 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, + 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, + // Remaining Balance Owner Threshold: + 0x6d, 0x6e, 0x6f, 0x70, + // Remaining Balance Owner Addresses Length: + 0x00, 0x00, 0x00, 0x01, + // Remaining Balance Owner Address[0]: + 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, + 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0x80, + 0x81, 0x82, 0x83, 0x84, + // Disable Owner Threshold: + 0x85, 0x86, 0x87, 0x88, + // Disable Owner Addresses Length: + 0x00, 0x00, 0x00, 0x01, + // Disable Owner Address[0]: + 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, + 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, + 0x99, 0x9a, 0x9b, 0x9c, + // Weight: + 0x9d, 0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, }, expected: mustCreate(NewRegisterSubnetValidator( ids.ID{ @@ -91,16 +109,36 @@ func TestParse(t *testing.T) { 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, }, - 0x35363738393a3b3c, [bls.PublicKeyLen]byte{ + 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, 0x64, - 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, }, - 0x6d6e6f7071727374, + 0x65666768696a6b6c, + PChainOwner{ + Threshold: 0x6d6e6f70, + Addresses: []ids.ShortID{ + { + 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, + 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0x80, + 0x81, 0x82, 0x83, 0x84, + }, + }, + }, + PChainOwner{ + Threshold: 0x85868788, + Addresses: []ids.ShortID{ + { + 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, + 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, + 0x99, 0x9a, 0x9b, 0x9c, + }, + }, + }, + 0x9d9e9fa0a1a2a3a4, )), }, { @@ -128,41 +166,13 @@ func TestParse(t *testing.T) { false, )), }, - { - name: "SetSubnetValidatorWeight", - bytes: []byte{ - // Codec version: - 0x00, 0x00, - // Payload type = SetSubnetValidatorWeight: - 0x00, 0x00, 0x00, 0x03, - // ValidationID: - 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, - 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, - 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, - 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, - // Nonce: - 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, - // Weight: - 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, - }, - expected: mustCreate(NewSetSubnetValidatorWeight( - ids.ID{ - 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, - 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, - 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, - 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, - }, - 0x2122232425262728, - 0x292a2b2c2d2e2f30, - )), - }, { name: "SubnetValidatorWeight", bytes: []byte{ // Codec version: 0x00, 0x00, // Payload type = SubnetValidatorWeight: - 0x00, 0x00, 0x00, 0x04, + 0x00, 0x00, 0x00, 0x03, // ValidationID: 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, diff --git a/vms/platformvm/warp/message/register_subnet_validator.go b/vms/platformvm/warp/message/register_subnet_validator.go index 4a6bd9240435..cf0b1cbcd569 100644 --- a/vms/platformvm/warp/message/register_subnet_validator.go +++ b/vms/platformvm/warp/message/register_subnet_validator.go @@ -4,38 +4,100 @@ package message import ( + "errors" "fmt" "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/constants" "github.com/ava-labs/avalanchego/utils/crypto/bls" + "github.com/ava-labs/avalanchego/utils/hashing" + "github.com/ava-labs/avalanchego/vms/components/verify" + "github.com/ava-labs/avalanchego/vms/secp256k1fx" + "github.com/ava-labs/avalanchego/vms/types" ) +var ( + ErrInvalidSubnetID = errors.New("invalid subnet ID") + ErrInvalidWeight = errors.New("invalid weight") + ErrInvalidNodeID = errors.New("invalid node ID") + ErrInvalidOwner = errors.New("invalid owner") +) + +type PChainOwner struct { + // The threshold number of `Addresses` that must provide a signature in + // order for the `PChainOwner` to be considered valid. + Threshold uint32 `serialize:"true" json:"threshold"` + // The addresses that are allowed to sign to authenticate a `PChainOwner`. + Addresses []ids.ShortID `serialize:"true" json:"addresses"` +} + // RegisterSubnetValidator adds a validator to the subnet. type RegisterSubnetValidator struct { payload - SubnetID ids.ID `serialize:"true" json:"subnetID"` - // TODO: Use a 32-byte nodeID here - NodeID ids.NodeID `serialize:"true" json:"nodeID"` - Weight uint64 `serialize:"true" json:"weight"` - BLSPublicKey [bls.PublicKeyLen]byte `serialize:"true" json:"blsPublicKey"` - Expiry uint64 `serialize:"true" json:"expiry"` + SubnetID ids.ID `serialize:"true" json:"subnetID"` + NodeID types.JSONByteSlice `serialize:"true" json:"nodeID"` + BLSPublicKey [bls.PublicKeyLen]byte `serialize:"true" json:"blsPublicKey"` + Expiry uint64 `serialize:"true" json:"expiry"` + RemainingBalanceOwner PChainOwner `serialize:"true" json:"remainingBalanceOwner"` + DisableOwner PChainOwner `serialize:"true" json:"disableOwner"` + Weight uint64 `serialize:"true" json:"weight"` +} + +func (r *RegisterSubnetValidator) Verify() error { + if r.SubnetID == constants.PrimaryNetworkID { + return ErrInvalidSubnetID + } + if r.Weight == 0 { + return ErrInvalidWeight + } + + nodeID, err := ids.ToNodeID(r.NodeID) + if err != nil { + return fmt.Errorf("%w: %w", ErrInvalidNodeID, err) + } + if nodeID == ids.EmptyNodeID { + return fmt.Errorf("%w: empty nodeID is disallowed", ErrInvalidNodeID) + } + + err = verify.All( + &secp256k1fx.OutputOwners{ + Threshold: r.RemainingBalanceOwner.Threshold, + Addrs: r.RemainingBalanceOwner.Addresses, + }, + &secp256k1fx.OutputOwners{ + Threshold: r.DisableOwner.Threshold, + Addrs: r.DisableOwner.Addresses, + }, + ) + if err != nil { + return fmt.Errorf("%w: %w", ErrInvalidOwner, err) + } + return nil +} + +func (r *RegisterSubnetValidator) ValidationID() ids.ID { + return hashing.ComputeHash256Array(r.Bytes()) } // NewRegisterSubnetValidator creates a new initialized RegisterSubnetValidator. func NewRegisterSubnetValidator( subnetID ids.ID, nodeID ids.NodeID, - weight uint64, blsPublicKey [bls.PublicKeyLen]byte, expiry uint64, + remainingBalanceOwner PChainOwner, + disableOwner PChainOwner, + weight uint64, ) (*RegisterSubnetValidator, error) { msg := &RegisterSubnetValidator{ - SubnetID: subnetID, - NodeID: nodeID, - Weight: weight, - BLSPublicKey: blsPublicKey, - Expiry: expiry, + SubnetID: subnetID, + NodeID: nodeID[:], + BLSPublicKey: blsPublicKey, + Expiry: expiry, + RemainingBalanceOwner: remainingBalanceOwner, + DisableOwner: disableOwner, + Weight: weight, } return msg, initialize(msg) } diff --git a/vms/platformvm/warp/message/register_subnet_validator_test.go b/vms/platformvm/warp/message/register_subnet_validator_test.go index 641569ae6e61..68898e47979f 100644 --- a/vms/platformvm/warp/message/register_subnet_validator_test.go +++ b/vms/platformvm/warp/message/register_subnet_validator_test.go @@ -10,7 +10,9 @@ import ( "github.com/stretchr/testify/require" "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/constants" "github.com/ava-labs/avalanchego/utils/crypto/bls" + "github.com/ava-labs/avalanchego/utils/hashing" ) func newBLSPublicKey(t *testing.T) [bls.PublicKeyLen]byte { @@ -28,13 +30,168 @@ func TestRegisterSubnetValidator(t *testing.T) { msg, err := NewRegisterSubnetValidator( ids.GenerateTestID(), ids.GenerateTestNodeID(), - rand.Uint64(), //#nosec G404 newBLSPublicKey(t), rand.Uint64(), //#nosec G404 + PChainOwner{ + Threshold: rand.Uint32(), //#nosec G404 + Addresses: []ids.ShortID{ + ids.GenerateTestShortID(), + }, + }, + PChainOwner{ + Threshold: rand.Uint32(), //#nosec G404 + Addresses: []ids.ShortID{ + ids.GenerateTestShortID(), + }, + }, + rand.Uint64(), //#nosec G404 ) require.NoError(err) - parsed, err := ParseRegisterSubnetValidator(msg.Bytes()) + bytes := msg.Bytes() + var expectedValidationID ids.ID = hashing.ComputeHash256Array(bytes) + require.Equal(expectedValidationID, msg.ValidationID()) + + parsed, err := ParseRegisterSubnetValidator(bytes) require.NoError(err) require.Equal(msg, parsed) } + +func TestRegisterSubnetValidator_Verify(t *testing.T) { + mustCreate := func(msg *RegisterSubnetValidator, err error) *RegisterSubnetValidator { + require.NoError(t, err) + return msg + } + tests := []struct { + name string + msg *RegisterSubnetValidator + expected error + }{ + { + name: "PrimaryNetworkID", + msg: mustCreate(NewRegisterSubnetValidator( + constants.PrimaryNetworkID, + ids.GenerateTestNodeID(), + newBLSPublicKey(t), + rand.Uint64(), //#nosec G404 + PChainOwner{ + Threshold: 1, + Addresses: []ids.ShortID{ + ids.GenerateTestShortID(), + }, + }, + PChainOwner{ + Threshold: 0, + }, + 1, + )), + expected: ErrInvalidSubnetID, + }, + { + name: "Weight = 0", + msg: mustCreate(NewRegisterSubnetValidator( + ids.GenerateTestID(), + ids.GenerateTestNodeID(), + newBLSPublicKey(t), + rand.Uint64(), //#nosec G404 + PChainOwner{ + Threshold: 1, + Addresses: []ids.ShortID{ + ids.GenerateTestShortID(), + }, + }, + PChainOwner{ + Threshold: 0, + }, + 0, + )), + expected: ErrInvalidWeight, + }, + { + name: "Invalid NodeID Length", + msg: &RegisterSubnetValidator{ + SubnetID: ids.GenerateTestID(), + NodeID: nil, + BLSPublicKey: newBLSPublicKey(t), + Expiry: rand.Uint64(), //#nosec G404 + RemainingBalanceOwner: PChainOwner{ + Threshold: 1, + Addresses: []ids.ShortID{ + ids.GenerateTestShortID(), + }, + }, + DisableOwner: PChainOwner{ + Threshold: 0, + }, + Weight: 1, + }, + expected: ErrInvalidNodeID, + }, + { + name: "Invalid NodeID", + msg: mustCreate(NewRegisterSubnetValidator( + ids.GenerateTestID(), + ids.EmptyNodeID, + newBLSPublicKey(t), + rand.Uint64(), //#nosec G404 + PChainOwner{ + Threshold: 1, + Addresses: []ids.ShortID{ + ids.GenerateTestShortID(), + }, + }, + PChainOwner{ + Threshold: 0, + }, + 1, + )), + expected: ErrInvalidNodeID, + }, + { + name: "Invalid Owner", + msg: mustCreate(NewRegisterSubnetValidator( + ids.GenerateTestID(), + ids.GenerateTestNodeID(), + newBLSPublicKey(t), + rand.Uint64(), //#nosec G404 + PChainOwner{ + Threshold: 0, + Addresses: []ids.ShortID{ + ids.GenerateTestShortID(), + }, + }, + PChainOwner{ + Threshold: 0, + }, + 1, + )), + expected: ErrInvalidOwner, + }, + { + name: "Valid", + msg: mustCreate(NewRegisterSubnetValidator( + ids.GenerateTestID(), + ids.GenerateTestNodeID(), + newBLSPublicKey(t), + rand.Uint64(), //#nosec G404 + PChainOwner{ + Threshold: 1, + Addresses: []ids.ShortID{ + ids.GenerateTestShortID(), + }, + }, + PChainOwner{ + Threshold: 0, + }, + 1, + )), + expected: nil, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := test.msg.Verify() + require.ErrorIs(t, err, test.expected) + }) + } +} diff --git a/vms/platformvm/warp/message/set_subnet_validator_weight.go b/vms/platformvm/warp/message/set_subnet_validator_weight.go deleted file mode 100644 index bde19f7ae67a..000000000000 --- a/vms/platformvm/warp/message/set_subnet_validator_weight.go +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package message - -import ( - "fmt" - - "github.com/ava-labs/avalanchego/ids" -) - -// SetSubnetValidatorWeight updates the weight of the specified validator. -type SetSubnetValidatorWeight struct { - payload - - ValidationID ids.ID `serialize:"true" json:"validationID"` - Nonce uint64 `serialize:"true" json:"nonce"` - Weight uint64 `serialize:"true" json:"weight"` -} - -// NewSetSubnetValidatorWeight creates a new initialized -// SetSubnetValidatorWeight. -func NewSetSubnetValidatorWeight( - validationID ids.ID, - nonce uint64, - weight uint64, -) (*SetSubnetValidatorWeight, error) { - msg := &SetSubnetValidatorWeight{ - ValidationID: validationID, - Nonce: nonce, - Weight: weight, - } - return msg, initialize(msg) -} - -// ParseSetSubnetValidatorWeight parses bytes into an initialized -// SetSubnetValidatorWeight. -func ParseSetSubnetValidatorWeight(b []byte) (*SetSubnetValidatorWeight, error) { - payloadIntf, err := Parse(b) - if err != nil { - return nil, err - } - payload, ok := payloadIntf.(*SetSubnetValidatorWeight) - if !ok { - return nil, fmt.Errorf("%w: %T", ErrWrongType, payloadIntf) - } - return payload, nil -} diff --git a/vms/platformvm/warp/message/set_subnet_validator_weight_test.go b/vms/platformvm/warp/message/set_subnet_validator_weight_test.go deleted file mode 100644 index 9e54cf2e34b2..000000000000 --- a/vms/platformvm/warp/message/set_subnet_validator_weight_test.go +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package message - -import ( - "math/rand" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/ava-labs/avalanchego/ids" -) - -func TestSetSubnetValidatorWeight(t *testing.T) { - require := require.New(t) - - msg, err := NewSetSubnetValidatorWeight( - ids.GenerateTestID(), - rand.Uint64(), //#nosec G404 - rand.Uint64(), //#nosec G404 - ) - require.NoError(err) - - parsed, err := ParseSetSubnetValidatorWeight(msg.Bytes()) - require.NoError(err) - require.Equal(msg, parsed) -} diff --git a/vms/platformvm/warp/message/subnet_conversion.go b/vms/platformvm/warp/message/subnet_conversion.go index d4ca5587453d..a93354fbe446 100644 --- a/vms/platformvm/warp/message/subnet_conversion.go +++ b/vms/platformvm/warp/message/subnet_conversion.go @@ -7,13 +7,41 @@ import ( "fmt" "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/crypto/bls" + "github.com/ava-labs/avalanchego/utils/hashing" + "github.com/ava-labs/avalanchego/vms/types" ) -// SubnetConversion reports summary of the subnet conversation that occurred on -// the P-chain. +type SubnetConversionValidatorData struct { + NodeID types.JSONByteSlice `serialize:"true" json:"nodeID"` + BLSPublicKey [bls.PublicKeyLen]byte `serialize:"true" json:"blsPublicKey"` + Weight uint64 `serialize:"true" json:"weight"` +} + +type SubnetConversionData struct { + SubnetID ids.ID `serialize:"true" json:"subnetID"` + ManagerChainID ids.ID `serialize:"true" json:"managerChainID"` + ManagerAddress types.JSONByteSlice `serialize:"true" json:"managerAddress"` + Validators []SubnetConversionValidatorData `serialize:"true" json:"validators"` +} + +// SubnetConversionID creates a subnet conversion ID from the provided subnet +// conversion data. +func SubnetConversionID(data SubnetConversionData) (ids.ID, error) { + bytes, err := Codec.Marshal(CodecVersion, &data) + if err != nil { + return ids.Empty, err + } + return hashing.ComputeHash256Array(bytes), nil +} + +// SubnetConversion reports the summary of the subnet conversation that occurred +// on the P-chain. type SubnetConversion struct { payload + // ID of the subnet conversion. It is typically generated by calling + // SubnetConversionID. ID ids.ID `serialize:"true" json:"id"` } diff --git a/vms/platformvm/warp/message/subnet_conversion_test.go b/vms/platformvm/warp/message/subnet_conversion_test.go index ad823291eb09..1da07f75e344 100644 --- a/vms/platformvm/warp/message/subnet_conversion_test.go +++ b/vms/platformvm/warp/message/subnet_conversion_test.go @@ -9,8 +9,90 @@ import ( "github.com/stretchr/testify/require" "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/crypto/bls" + "github.com/ava-labs/avalanchego/utils/hashing" + "github.com/ava-labs/avalanchego/vms/types" ) +func TestSubnetConversionID(t *testing.T) { + require := require.New(t) + + subnetConversionDataBytes := []byte{ + // Codec version: + 0x00, 0x00, + // SubnetID: + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, + 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, + // ManagerChainID: + 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, + 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, + 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, + 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, + // ManagerAddress Length: + 0x00, 0x00, 0x00, 0x01, + // ManagerAddress: + 0x41, + // Validators Length: + 0x00, 0x00, 0x00, 0x01, + // Validator[0]: + // NodeID Length: + 0x00, 0x00, 0x00, 0x14, + // NodeID: + 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, + 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, + 0x52, 0x53, 0x54, 0x55, + // BLSPublicKey: + 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, + 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, + 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, + 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, + 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, + 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, + // Weight: + 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, + } + var expectedSubnetConversionID ids.ID = hashing.ComputeHash256Array(subnetConversionDataBytes) + + subnetConversionData := SubnetConversionData{ + SubnetID: ids.ID{ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, + 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, + }, + ManagerChainID: ids.ID{ + 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, + 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, + 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, + 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, + }, + ManagerAddress: []byte{0x41}, + Validators: []SubnetConversionValidatorData{ + { + NodeID: types.JSONByteSlice([]byte{ + 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, + 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, + 0x52, 0x53, 0x54, 0x55, + }), + BLSPublicKey: [bls.PublicKeyLen]byte{ + 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, + 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, + 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, + 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, + 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, + 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, + }, + Weight: 0x868788898a8b8c8d, + }, + }, + } + subnetConversionID, err := SubnetConversionID(subnetConversionData) + require.NoError(err) + require.Equal(expectedSubnetConversionID, subnetConversionID) +} + func TestSubnetConversion(t *testing.T) { require := require.New(t) diff --git a/vms/platformvm/warp/message/subnet_validator_weight.go b/vms/platformvm/warp/message/subnet_validator_weight.go index 4091ff9af8ac..dcfa6c5a16c0 100644 --- a/vms/platformvm/warp/message/subnet_validator_weight.go +++ b/vms/platformvm/warp/message/subnet_validator_weight.go @@ -4,13 +4,22 @@ package message import ( + "errors" "fmt" + "math" "github.com/ava-labs/avalanchego/ids" ) -// SubnetValidatorWeight reports the current nonce and weight of a validator -// registered on the P-chain. +var ErrNonceReservedForRemoval = errors.New("maxUint64 nonce is reserved for removal") + +// SubnetValidatorWeight is both received and sent by the P-chain. +// +// If the P-chain is receiving this message, it is treated as a command to +// update the weight of the validator. +// +// If the P-chain is sending this message, it is reporting the current nonce and +// weight of the validator. type SubnetValidatorWeight struct { payload @@ -19,6 +28,13 @@ type SubnetValidatorWeight struct { Weight uint64 `serialize:"true" json:"weight"` } +func (s *SubnetValidatorWeight) Verify() error { + if s.Nonce == math.MaxUint64 && s.Weight != 0 { + return ErrNonceReservedForRemoval + } + return nil +} + // NewSubnetValidatorWeight creates a new initialized SubnetValidatorWeight. func NewSubnetValidatorWeight( validationID ids.ID, diff --git a/vms/platformvm/warp/message/subnet_validator_weight_test.go b/vms/platformvm/warp/message/subnet_validator_weight_test.go index faa67ae5c384..17cef30b88cb 100644 --- a/vms/platformvm/warp/message/subnet_validator_weight_test.go +++ b/vms/platformvm/warp/message/subnet_validator_weight_test.go @@ -4,6 +4,7 @@ package message import ( + "math" "math/rand" "testing" @@ -26,3 +27,40 @@ func TestSubnetValidatorWeight(t *testing.T) { require.NoError(err) require.Equal(msg, parsed) } + +func TestSubnetValidatorWeight_Verify(t *testing.T) { + mustCreate := func(msg *SubnetValidatorWeight, err error) *SubnetValidatorWeight { + require.NoError(t, err) + return msg + } + tests := []struct { + name string + msg *SubnetValidatorWeight + expected error + }{ + { + name: "Invalid Nonce", + msg: mustCreate(NewSubnetValidatorWeight( + ids.GenerateTestID(), + math.MaxUint64, + 1, + )), + expected: ErrNonceReservedForRemoval, + }, + { + name: "Valid", + msg: mustCreate(NewSubnetValidatorWeight( + ids.GenerateTestID(), + math.MaxUint64, + 0, + )), + expected: nil, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := test.msg.Verify() + require.ErrorIs(t, err, test.expected) + }) + } +}