mirror of
https://github.com/luxfi/consensus.git
synced 2026-07-27 02:57:46 +00:00
refactor: move bag from utils/ to types/ for semantic clarity
Rationale: Bag is a fundamental data type, not a utility Changes: - Moved utils/bag/ → types/bag/ - Updated imports in error_propagation_test.go - Updated imports in network_test.go - Updated package documentation Why types/ over utils/: - Bag is a multiset data structure (fundamental type) - SetThreshold/Threshold methods are type-level features - More semantic: types contain data structures, utils contain helpers Canonical import: github.com/luxfi/consensus/types/bag All tests passing: - ✅ Chain engine: 28 tests (0.438s) - ✅ Integration: all tests (59s) Next: Node should kill utils/bag and import consensus/types/bag
This commit is contained in:
@@ -15,7 +15,7 @@ import (
|
||||
|
||||
"github.com/luxfi/consensus/engine/chain/block"
|
||||
"github.com/luxfi/consensus/engine/chain/chaintest"
|
||||
"github.com/luxfi/consensus/utils/bag"
|
||||
"github.com/luxfi/consensus/types/bag"
|
||||
"github.com/luxfi/ids"
|
||||
)
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
|
||||
"github.com/luxfi/consensus/core/choices"
|
||||
"github.com/luxfi/consensus/test/helpers"
|
||||
"github.com/luxfi/consensus/utils/bag"
|
||||
"github.com/luxfi/consensus/types/bag"
|
||||
"github.com/luxfi/consensus/utils/set"
|
||||
"github.com/luxfi/ids"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
// Package bag provides utilities for vote collection and counting.
|
||||
// This is the canonical bag implementation for Lux consensus.
|
||||
// Other packages should import from github.com/luxfi/consensus/utils/bag
|
||||
// Package bag provides multiset data structures for vote collection and counting.
|
||||
// This is the canonical bag implementation for all Lux packages.
|
||||
// Import from: github.com/luxfi/consensus/types/bag
|
||||
package bag
|
||||
|
||||
import (
|
||||
@@ -0,0 +1,157 @@
|
||||
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package validators
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/exp/maps"
|
||||
|
||||
"github.com/luxfi/ids"
|
||||
"github.com/luxfi/node/utils"
|
||||
"github.com/luxfi/node/utils/crypto/bls"
|
||||
"github.com/luxfi/node/utils/math"
|
||||
"github.com/luxfi/math/set"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrUnknownValidator = errors.New("unknown validator")
|
||||
ErrWeightOverflow = errors.New("weight overflowed")
|
||||
)
|
||||
|
||||
// CanonicalValidatorSet represents a validator set in canonical ordering
|
||||
type CanonicalValidatorSet struct {
|
||||
// Validators slice in canonical ordering of the validators that has public key
|
||||
Validators []*CanonicalValidator
|
||||
// The total weight of all the validators, including the ones that doesn't have a public key
|
||||
TotalWeight uint64
|
||||
}
|
||||
|
||||
// CanonicalValidator represents a single validator with BLS public key in canonical form
|
||||
type CanonicalValidator struct {
|
||||
PublicKey *bls.PublicKey
|
||||
PublicKeyBytes []byte // Uncompressed bytes for canonical ordering
|
||||
Weight uint64
|
||||
NodeIDs []ids.NodeID // Can have multiple NodeIDs with same public key
|
||||
}
|
||||
|
||||
// Compare implements utils.Sortable for canonical ordering
|
||||
func (v *CanonicalValidator) Compare(o *CanonicalValidator) int {
|
||||
return bytes.Compare(v.PublicKeyBytes, o.PublicKeyBytes)
|
||||
}
|
||||
|
||||
var _ utils.Sortable[*CanonicalValidator] = (*CanonicalValidator)(nil)
|
||||
|
||||
// FlattenValidatorSet converts the provided [vdrSet] into a canonical ordering.
|
||||
// Also returns the total weight of the validator set.
|
||||
func FlattenValidatorSet(vdrSet map[ids.NodeID]*GetValidatorOutput) (CanonicalValidatorSet, error) {
|
||||
var (
|
||||
// Map public keys to validators to handle duplicates
|
||||
pkToValidator = make(map[string]*CanonicalValidator)
|
||||
totalWeight uint64
|
||||
err error
|
||||
)
|
||||
for _, vdr := range vdrSet {
|
||||
totalWeight, err = math.Add(totalWeight, vdr.Weight)
|
||||
if err != nil {
|
||||
return CanonicalValidatorSet{}, fmt.Errorf("%w: %w", ErrWeightOverflow, err)
|
||||
}
|
||||
|
||||
// Skip validators without public keys
|
||||
if len(vdr.PublicKey) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// Convert []byte to *bls.PublicKey
|
||||
blsPK, err := bls.PublicKeyFromCompressedBytes(vdr.PublicKey)
|
||||
if err != nil {
|
||||
continue // Skip invalid public keys
|
||||
}
|
||||
|
||||
// Use uncompressed bytes as the canonical key representation
|
||||
pkBytes := bls.PublicKeyToUncompressedBytes(blsPK)
|
||||
pkKey := string(pkBytes)
|
||||
|
||||
// Check if we already have a validator with this public key
|
||||
if existingVdr, exists := pkToValidator[pkKey]; exists {
|
||||
// Merge validators with duplicate public keys
|
||||
existingVdr.Weight, err = math.Add(existingVdr.Weight, vdr.Weight)
|
||||
if err != nil {
|
||||
return CanonicalValidatorSet{}, fmt.Errorf("%w: %w", ErrWeightOverflow, err)
|
||||
}
|
||||
existingVdr.NodeIDs = append(existingVdr.NodeIDs, vdr.NodeID)
|
||||
} else {
|
||||
// Create new validator
|
||||
newVdr := &CanonicalValidator{
|
||||
PublicKey: blsPK,
|
||||
PublicKeyBytes: pkBytes,
|
||||
Weight: vdr.Weight,
|
||||
NodeIDs: []ids.NodeID{vdr.NodeID},
|
||||
}
|
||||
pkToValidator[pkKey] = newVdr
|
||||
}
|
||||
}
|
||||
|
||||
// Sort validators by public key
|
||||
vdrList := maps.Values(pkToValidator)
|
||||
utils.Sort(vdrList)
|
||||
return CanonicalValidatorSet{Validators: vdrList, TotalWeight: totalWeight}, nil
|
||||
}
|
||||
|
||||
// FilterValidators returns the validators in [vdrs] whose bit is set to 1 in
|
||||
// [indices].
|
||||
//
|
||||
// Returns an error if [indices] references an unknown validator.
|
||||
func FilterValidators(
|
||||
indices set.Bits,
|
||||
vdrs []*CanonicalValidator,
|
||||
) ([]*CanonicalValidator, error) {
|
||||
// Verify that all alleged signers exist
|
||||
if indices.BitLen() > len(vdrs) {
|
||||
return nil, fmt.Errorf(
|
||||
"%w: NumIndices (%d) >= NumFilteredValidators (%d)",
|
||||
ErrUnknownValidator,
|
||||
indices.BitLen()-1, // -1 to convert from length to index
|
||||
len(vdrs),
|
||||
)
|
||||
}
|
||||
|
||||
filteredVdrs := make([]*CanonicalValidator, 0, len(vdrs))
|
||||
for i, vdr := range vdrs {
|
||||
if !indices.Contains(i) {
|
||||
continue
|
||||
}
|
||||
|
||||
filteredVdrs = append(filteredVdrs, vdr)
|
||||
}
|
||||
return filteredVdrs, nil
|
||||
}
|
||||
|
||||
// SumWeight returns the total weight of the provided validators.
|
||||
func SumWeight(vdrs []*CanonicalValidator) (uint64, error) {
|
||||
var (
|
||||
weight uint64
|
||||
err error
|
||||
)
|
||||
for _, vdr := range vdrs {
|
||||
weight, err = math.Add(weight, vdr.Weight)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("%w: %w", ErrWeightOverflow, err)
|
||||
}
|
||||
}
|
||||
return weight, nil
|
||||
}
|
||||
|
||||
// AggregatePublicKeys returns the public key of the provided validators.
|
||||
//
|
||||
// Invariant: All of the public keys in [vdrs] are valid.
|
||||
func AggregatePublicKeys(vdrs []*CanonicalValidator) (*bls.PublicKey, error) {
|
||||
pks := make([]*bls.PublicKey, len(vdrs))
|
||||
for i, vdr := range vdrs {
|
||||
pks[i] = vdr.PublicKey
|
||||
}
|
||||
return bls.AggregatePublicKeys(pks)
|
||||
}
|
||||
Reference in New Issue
Block a user