-
Notifications
You must be signed in to change notification settings - Fork 670
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: Dhruba Basu <[email protected]> Co-authored-by: dhrubabasu <[email protected]>
- Loading branch information
1 parent
92820ce
commit 5f04349
Showing
2 changed files
with
209 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. | ||
// See the file LICENSE for licensing terms. | ||
|
||
package state | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/google/btree" | ||
|
||
"github.com/ava-labs/avalanchego/database" | ||
"github.com/ava-labs/avalanchego/ids" | ||
"github.com/ava-labs/avalanchego/vms/platformvm/block" | ||
) | ||
|
||
var _ btree.LessFunc[*SubnetOnlyValidator] = (*SubnetOnlyValidator).Less | ||
|
||
type SubnetOnlyValidator struct { | ||
// ValidationID is not serialized because it is used as the key in the | ||
// database, so it doesn't need to be stored in the value. | ||
ValidationID ids.ID | ||
|
||
SubnetID ids.ID `serialize:"true"` | ||
NodeID ids.NodeID `serialize:"true"` | ||
|
||
// PublicKey is the uncompressed BLS public key of the validator. It is | ||
// guaranteed to be populated. | ||
PublicKey []byte `serialize:"true"` | ||
|
||
// StartTime is the unix timestamp, in seconds, when this validator was | ||
// added to the set. | ||
StartTime uint64 `serialize:"true"` | ||
|
||
// Weight of this validator. It can be updated when the MinNonce is | ||
// increased. If the weight is being set to 0, the validator is being | ||
// removed. | ||
Weight uint64 `serialize:"true"` | ||
|
||
// MinNonce is the smallest nonce that can be used to modify this | ||
// validator's weight. It is initially set to 0 and is set to one higher | ||
// than the last nonce used. It is not valid to use nonce MaxUint64 unless | ||
// the weight is being set to 0, which removes the validator from the set. | ||
MinNonce uint64 `serialize:"true"` | ||
|
||
// EndAccumulatedFee is the amount of globally accumulated fees that can | ||
// accrue before this validator must be deactivated. It is equal to the | ||
// amount of fees this validator is willing to pay plus the amount of | ||
// globally accumulated fees when this validator started validating. | ||
EndAccumulatedFee uint64 `serialize:"true"` | ||
} | ||
|
||
// Less determines a canonical ordering of *SubnetOnlyValidators based on their | ||
// EndAccumulatedFees and ValidationIDs. | ||
// | ||
// Returns true if: | ||
// | ||
// 1. This validator has a lower EndAccumulatedFee than the other. | ||
// 2. This validator has an equal EndAccumulatedFee to the other and has a | ||
// lexicographically lower ValidationID. | ||
func (v *SubnetOnlyValidator) Less(o *SubnetOnlyValidator) bool { | ||
switch { | ||
case v.EndAccumulatedFee < o.EndAccumulatedFee: | ||
return true | ||
case o.EndAccumulatedFee < v.EndAccumulatedFee: | ||
return false | ||
default: | ||
return v.ValidationID.Compare(o.ValidationID) == -1 | ||
} | ||
} | ||
|
||
func getSubnetOnlyValidator(db database.KeyValueReader, validationID ids.ID) (*SubnetOnlyValidator, error) { | ||
bytes, err := db.Get(validationID[:]) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
vdr := &SubnetOnlyValidator{ | ||
ValidationID: validationID, | ||
} | ||
if _, err = block.GenesisCodec.Unmarshal(bytes, vdr); err != nil { | ||
return nil, fmt.Errorf("failed to unmarshal SubnetOnlyValidator: %w", err) | ||
} | ||
return vdr, err | ||
} | ||
|
||
func putSubnetOnlyValidator(db database.KeyValueWriter, vdr *SubnetOnlyValidator) error { | ||
bytes, err := block.GenesisCodec.Marshal(block.CodecVersion, vdr) | ||
if err != nil { | ||
return fmt.Errorf("failed to marshal SubnetOnlyValidator: %w", err) | ||
} | ||
return db.Put(vdr.ValidationID[:], bytes) | ||
} | ||
|
||
func deleteSubnetOnlyValidator(db database.KeyValueDeleter, validationID ids.ID) error { | ||
return db.Delete(validationID[:]) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. | ||
// See the file LICENSE for licensing terms. | ||
|
||
package state | ||
|
||
import ( | ||
"math/rand" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/require" | ||
|
||
"github.com/ava-labs/avalanchego/database" | ||
"github.com/ava-labs/avalanchego/database/memdb" | ||
"github.com/ava-labs/avalanchego/ids" | ||
"github.com/ava-labs/avalanchego/utils/crypto/bls" | ||
) | ||
|
||
func TestSubnetOnlyValidator_Less(t *testing.T) { | ||
tests := []struct { | ||
name string | ||
v *SubnetOnlyValidator | ||
o *SubnetOnlyValidator | ||
equal bool | ||
}{ | ||
{ | ||
name: "v.EndAccumulatedFee < o.EndAccumulatedFee", | ||
v: &SubnetOnlyValidator{ | ||
ValidationID: ids.GenerateTestID(), | ||
EndAccumulatedFee: 1, | ||
}, | ||
o: &SubnetOnlyValidator{ | ||
ValidationID: ids.GenerateTestID(), | ||
EndAccumulatedFee: 2, | ||
}, | ||
equal: false, | ||
}, | ||
{ | ||
name: "v.EndAccumulatedFee = o.EndAccumulatedFee, v.ValidationID < o.ValidationID", | ||
v: &SubnetOnlyValidator{ | ||
ValidationID: ids.ID{0}, | ||
EndAccumulatedFee: 1, | ||
}, | ||
o: &SubnetOnlyValidator{ | ||
ValidationID: ids.ID{1}, | ||
EndAccumulatedFee: 1, | ||
}, | ||
equal: false, | ||
}, | ||
{ | ||
name: "v.EndAccumulatedFee = o.EndAccumulatedFee, v.ValidationID = o.ValidationID", | ||
v: &SubnetOnlyValidator{ | ||
ValidationID: ids.ID{0}, | ||
EndAccumulatedFee: 1, | ||
}, | ||
o: &SubnetOnlyValidator{ | ||
ValidationID: ids.ID{0}, | ||
EndAccumulatedFee: 1, | ||
}, | ||
equal: true, | ||
}, | ||
} | ||
for _, test := range tests { | ||
t.Run(test.name, func(t *testing.T) { | ||
require := require.New(t) | ||
|
||
less := test.v.Less(test.o) | ||
require.Equal(!test.equal, less) | ||
|
||
greater := test.o.Less(test.v) | ||
require.False(greater) | ||
}) | ||
} | ||
} | ||
|
||
func TestSubnetOnlyValidator_DatabaseHelpers(t *testing.T) { | ||
require := require.New(t) | ||
db := memdb.New() | ||
|
||
sk, err := bls.NewSecretKey() | ||
require.NoError(err) | ||
|
||
vdr := &SubnetOnlyValidator{ | ||
ValidationID: ids.GenerateTestID(), | ||
SubnetID: ids.GenerateTestID(), | ||
NodeID: ids.GenerateTestNodeID(), | ||
PublicKey: bls.PublicKeyToUncompressedBytes(bls.PublicFromSecretKey(sk)), | ||
StartTime: rand.Uint64(), // #nosec G404 | ||
Weight: rand.Uint64(), // #nosec G404 | ||
MinNonce: rand.Uint64(), // #nosec G404 | ||
EndAccumulatedFee: rand.Uint64(), // #nosec G404 | ||
} | ||
|
||
// Validator hasn't been put on disk yet | ||
gotVdr, err := getSubnetOnlyValidator(db, vdr.ValidationID) | ||
require.ErrorIs(err, database.ErrNotFound) | ||
require.Nil(gotVdr) | ||
|
||
// Place the validator on disk | ||
require.NoError(putSubnetOnlyValidator(db, vdr)) | ||
|
||
// Verify that the validator can be fetched from disk | ||
gotVdr, err = getSubnetOnlyValidator(db, vdr.ValidationID) | ||
require.NoError(err) | ||
require.Equal(vdr, gotVdr) | ||
|
||
// Remove the validator from disk | ||
require.NoError(deleteSubnetOnlyValidator(db, vdr.ValidationID)) | ||
|
||
// Verify that the validator has been removed from disk | ||
gotVdr, err = getSubnetOnlyValidator(db, vdr.ValidationID) | ||
require.ErrorIs(err, database.ErrNotFound) | ||
require.Nil(gotVdr) | ||
} |