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:
Hanzo Dev
2025-11-11 13:54:39 -08:00
parent b5ca50cce5
commit 1ce0ff8cf1
4 changed files with 162 additions and 5 deletions
+1 -1
View File
@@ -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"
)
+1 -1
View File
@@ -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"
+3 -3
View File
@@ -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 (
+157
View File
@@ -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)
}