github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/model/flow/identity_list.go (about)

     1  package flow
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"math"
     7  
     8  	"github.com/onflow/crypto"
     9  	"golang.org/x/exp/slices"
    10  
    11  	"github.com/onflow/flow-go/utils/rand"
    12  )
    13  
    14  // Notes on runtime EFFICIENCY of GENERIC TYPES:
    15  // DO NOT pass an interface to a generic function (100x runtime cost as of go 1.20).
    16  // For example, consider the function
    17  //
    18  //    func f[T GenericIdentity]()
    19  //
    20  // The call `f(identity)` is completely ok and doesn't introduce overhead when `identity` is a struct type,
    21  // such as `var identity *flow.Identity`.
    22  // In contrast `f(identity)` where identity is declared as an interface `var identity GenericIdentity` is drastically slower,
    23  // since golang involves a global hash table lookup for every method call to dispatch the underlying type behind the interface.
    24  
    25  // GenericIdentity defines a constraint for generic identities.
    26  // Golang doesn't support constraint with fields(for time being) so we have to define this interface
    27  // with getter methods.
    28  // Details here: https://github.com/golang/go/issues/51259.
    29  type GenericIdentity interface {
    30  	Identity | IdentitySkeleton
    31  	GetNodeID() Identifier
    32  	GetRole() Role
    33  	GetStakingPubKey() crypto.PublicKey
    34  	GetNetworkPubKey() crypto.PublicKey
    35  	GetInitialWeight() uint64
    36  	GetSkeleton() IdentitySkeleton
    37  }
    38  
    39  // IdentityFilter is a filter on identities. Mathematically, an IdentityFilter F
    40  // can be described as a function F: 𝓘 → 𝐼, where 𝓘 denotes the set of all identities
    41  // and 𝐼 ⊆ 𝓘. For an input identity i, F(i) returns true if and only if i passed the
    42  // filter, i.e. i ∈ 𝐼. Returning false means that some necessary criterion was violated
    43  // and identity i should be dropped, i.e. i ∉ 𝐼.
    44  type IdentityFilter[T GenericIdentity] func(*T) bool
    45  
    46  // IdentityOrder is an order function for identities.
    47  //
    48  // It defines a strict weak ordering between identities.
    49  // It returns a negative number if the first identity is "strictly less" than the second,
    50  // a positive number if the second identity is "strictly less" than the first,
    51  // and zero if the two identities are equal.
    52  //
    53  // `IdentityOrder` can be used to sort identities with
    54  // https://pkg.go.dev/golang.org/x/exp/slices#SortFunc.
    55  type IdentityOrder[T GenericIdentity] func(*T, *T) int
    56  
    57  // IdentityMapFunc is a modifier function for map operations for identities.
    58  // Identities are COPIED from the source slice.
    59  type IdentityMapFunc[T GenericIdentity] func(T) T
    60  
    61  // IdentitySkeletonList is a list of nodes skeletons. We use a type alias instead of defining a new type
    62  // since go generics doesn't support implicit conversion between types.
    63  type IdentitySkeletonList = GenericIdentityList[IdentitySkeleton]
    64  
    65  // IdentityList is a list of nodes. We use a type alias instead of defining a new type
    66  // since go generics doesn't support implicit conversion between types.
    67  type IdentityList = GenericIdentityList[Identity]
    68  
    69  type GenericIdentityList[T GenericIdentity] []*T
    70  
    71  // Filter will apply a filter to the identity list.
    72  // The resulting list will only contain entries that match the filtering criteria.
    73  func (il GenericIdentityList[T]) Filter(filter IdentityFilter[T]) GenericIdentityList[T] {
    74  	var dup GenericIdentityList[T]
    75  	for _, identity := range il {
    76  		if filter(identity) {
    77  			dup = append(dup, identity)
    78  		}
    79  	}
    80  	return dup
    81  }
    82  
    83  // Map returns a new identity list with the map function f applied to a copy of
    84  // each identity.
    85  //
    86  // CAUTION: this relies on structure copy semantics. Map functions that modify
    87  // an object referenced by the input Identity structure will modify identities
    88  // in the source slice as well.
    89  func (il GenericIdentityList[T]) Map(f IdentityMapFunc[T]) GenericIdentityList[T] {
    90  	dup := make(GenericIdentityList[T], 0, len(il))
    91  	for _, identity := range il {
    92  		next := f(*identity)
    93  		dup = append(dup, &next)
    94  	}
    95  	return dup
    96  }
    97  
    98  // Copy returns a copy of IdentityList. The resulting slice uses a different
    99  // backing array, meaning appends and insert operations on either slice are
   100  // guaranteed to only affect that slice.
   101  //
   102  // Copy should be used when modifying an existing identity list by either
   103  // appending new elements, re-ordering, or inserting new elements in an
   104  // existing index.
   105  //
   106  // CAUTION:
   107  // All Identity fields are deep-copied, _except_ for their keys, which
   108  // are copied by reference as they are treated as immutable by convention.
   109  func (il GenericIdentityList[T]) Copy() GenericIdentityList[T] {
   110  	dup := make(GenericIdentityList[T], 0, len(il))
   111  	lenList := len(il)
   112  	for i := 0; i < lenList; i++ { // performance tests show this is faster than 'range'
   113  		next := *(il[i]) // copy the object
   114  		dup = append(dup, &next)
   115  	}
   116  	return dup
   117  }
   118  
   119  // Selector returns an identity filter function that selects only identities
   120  // within this identity list.
   121  func (il GenericIdentityList[T]) Selector() IdentityFilter[T] {
   122  	lookup := il.Lookup()
   123  	return func(identity *T) bool {
   124  		_, exists := lookup[(*identity).GetNodeID()]
   125  		return exists
   126  	}
   127  }
   128  
   129  // Lookup converts the identity slice to a map using the NodeIDs as keys. This
   130  // is useful when _repeatedly_ querying identities by their NodeIDs. The
   131  // conversation from slice to map incurs cost O(n), for `n` the slice length.
   132  // For a _single_ lookup, use method `ByNodeID(Identifier)` (avoiding conversion).
   133  func (il GenericIdentityList[T]) Lookup() map[Identifier]*T {
   134  	lookup := make(map[Identifier]*T, len(il))
   135  	for _, identity := range il {
   136  		lookup[(*identity).GetNodeID()] = identity
   137  	}
   138  	return lookup
   139  }
   140  
   141  // Sort will sort the list using the given ordering.  This is
   142  // not recommended for performance.  Expand the 'less' function
   143  // in place for best performance, and don't use this function.
   144  func (il GenericIdentityList[T]) Sort(less IdentityOrder[T]) GenericIdentityList[T] {
   145  	dup := il.Copy()
   146  	slices.SortFunc(dup, less)
   147  	return dup
   148  }
   149  
   150  // Sorted returns whether the list is sorted by the input ordering.
   151  func (il GenericIdentityList[T]) Sorted(less IdentityOrder[T]) bool {
   152  	return slices.IsSortedFunc(il, less)
   153  }
   154  
   155  // NodeIDs returns the NodeIDs of the nodes in the list (order preserving).
   156  func (il GenericIdentityList[T]) NodeIDs() IdentifierList {
   157  	nodeIDs := make([]Identifier, 0, len(il))
   158  	for _, id := range il {
   159  		nodeIDs = append(nodeIDs, (*id).GetNodeID())
   160  	}
   161  	return nodeIDs
   162  }
   163  
   164  // PublicStakingKeys returns a list with the public staking keys (order preserving).
   165  func (il GenericIdentityList[T]) PublicStakingKeys() []crypto.PublicKey {
   166  	pks := make([]crypto.PublicKey, 0, len(il))
   167  	for _, id := range il {
   168  		pks = append(pks, (*id).GetStakingPubKey())
   169  	}
   170  	return pks
   171  }
   172  
   173  // ID uniquely identifies a list of identities, by node ID. This can be used
   174  // to perpetually identify a group of nodes, even if mutable fields of some nodes
   175  // are changed, as node IDs are immutable.
   176  // CAUTION:
   177  //   - An IdentityList's ID is a cryptographic commitment to only node IDs. A node operator
   178  //     can freely choose the ID for their node. There is no relationship whatsoever between
   179  //     a node's ID and keys.
   180  //   - To generate a cryptographic commitment for the full IdentityList, use method `Checksum()`.
   181  //   - The outputs of `IdentityList.ID()` and `IdentityList.Checksum()` are both order-sensitive.
   182  //     Therefore, the `IdentityList` must be in canonical order, unless explicitly specified
   183  //     otherwise by the protocol.
   184  func (il GenericIdentityList[T]) ID() Identifier {
   185  	return il.NodeIDs().ID()
   186  }
   187  
   188  // Checksum generates a cryptographic commitment to the full IdentityList, including mutable fields.
   189  // The checksum for the same group of identities (by NodeID) may change from block to block.
   190  func (il GenericIdentityList[T]) Checksum() Identifier {
   191  	return MakeID(il)
   192  }
   193  
   194  // TotalWeight returns the total weight of all given identities.
   195  func (il GenericIdentityList[T]) TotalWeight() uint64 {
   196  	var total uint64
   197  	for _, identity := range il {
   198  		total += (*identity).GetInitialWeight()
   199  	}
   200  	return total
   201  }
   202  
   203  // Count returns the count of identities.
   204  func (il GenericIdentityList[T]) Count() uint {
   205  	return uint(len(il))
   206  }
   207  
   208  // ByIndex returns the node at the given index.
   209  func (il GenericIdentityList[T]) ByIndex(index uint) (*T, bool) {
   210  	if index >= uint(len(il)) {
   211  		return nil, false
   212  	}
   213  	return il[int(index)], true
   214  }
   215  
   216  // ByNodeID gets a node from the list by node ID.
   217  func (il GenericIdentityList[T]) ByNodeID(nodeID Identifier) (*T, bool) {
   218  	for _, identity := range il {
   219  		if (*identity).GetNodeID() == nodeID {
   220  			return identity, true
   221  		}
   222  	}
   223  	return nil, false
   224  }
   225  
   226  // ByNetworkingKey gets a node from the list by network public key.
   227  func (il GenericIdentityList[T]) ByNetworkingKey(key crypto.PublicKey) (*T, bool) {
   228  	for _, identity := range il {
   229  		if (*identity).GetNetworkPubKey().Equals(key) {
   230  			return identity, true
   231  		}
   232  	}
   233  	return nil, false
   234  }
   235  
   236  // Sample returns non-deterministic random sample from the `IdentityList`
   237  func (il GenericIdentityList[T]) Sample(size uint) (GenericIdentityList[T], error) {
   238  	n := uint(len(il))
   239  	dup := make(GenericIdentityList[T], 0, n)
   240  	dup = append(dup, il...)
   241  	if n < size {
   242  		size = n
   243  	}
   244  	swap := func(i, j uint) {
   245  		dup[i], dup[j] = dup[j], dup[i]
   246  	}
   247  	err := rand.Samples(n, size, swap)
   248  	if err != nil {
   249  		return nil, fmt.Errorf("failed to sample identity list: %w", err)
   250  	}
   251  	return dup[:size], nil
   252  }
   253  
   254  // Shuffle randomly shuffles the identity list (non-deterministic),
   255  // and returns the shuffled list without modifying the receiver.
   256  func (il GenericIdentityList[T]) Shuffle() (GenericIdentityList[T], error) {
   257  	return il.Sample(uint(len(il)))
   258  }
   259  
   260  // SamplePct returns a random sample from the receiver identity list. The
   261  // sample contains `pct` percentage of the list. The sample is rounded up
   262  // if `pct>0`, so this will always select at least one identity.
   263  //
   264  // NOTE: The input must be between in the interval [0, 1.0]
   265  func (il GenericIdentityList[T]) SamplePct(pct float64) (GenericIdentityList[T], error) {
   266  	if pct <= 0 {
   267  		return GenericIdentityList[T]{}, nil
   268  	}
   269  
   270  	count := float64(il.Count()) * pct
   271  	size := uint(math.Round(count))
   272  	// ensure we always select at least 1, for non-zero input
   273  	if size == 0 {
   274  		size = 1
   275  	}
   276  
   277  	return il.Sample(size)
   278  }
   279  
   280  // Union returns a new identity list containing every identity that occurs in
   281  // either `il`, or `other`, or both. There are no duplicates in the output,
   282  // where duplicates are identities with the same node ID. In case an entry
   283  // with the same NodeID exists in the receiver `il` as well as in `other`,
   284  // the identity from `il` is included in the output.
   285  // Receiver `il` and/or method input `other` can be nil or empty.
   286  // The returned IdentityList is sorted in canonical order.
   287  func (il GenericIdentityList[T]) Union(other GenericIdentityList[T]) GenericIdentityList[T] {
   288  	maxLen := len(il) + len(other)
   289  
   290  	union := make(GenericIdentityList[T], 0, maxLen)
   291  	set := make(map[Identifier]struct{}, maxLen)
   292  
   293  	for _, list := range []GenericIdentityList[T]{il, other} {
   294  		for _, id := range list {
   295  			if _, isDuplicate := set[(*id).GetNodeID()]; !isDuplicate {
   296  				set[(*id).GetNodeID()] = struct{}{}
   297  				union = append(union, id)
   298  			}
   299  		}
   300  	}
   301  
   302  	slices.SortFunc(union, Canonical[T])
   303  	return union
   304  }
   305  
   306  // IdentityListEqualTo checks if the other list if the same, that it contains the same elements
   307  // in the same order.
   308  // NOTE: currently a generic comparison is not possible, so we have to use a specific function.
   309  func IdentityListEqualTo(lhs, rhs IdentityList) bool {
   310  	return slices.EqualFunc(lhs, rhs, func(a, b *Identity) bool {
   311  		return a.EqualTo(b)
   312  	})
   313  }
   314  
   315  // IdentitySkeletonListEqualTo checks if the other list if the same, that it contains the same elements
   316  // in the same order.
   317  // NOTE: currently a generic comparison is not possible, so we have to use a specific function.
   318  func IdentitySkeletonListEqualTo(lhs, rhs IdentitySkeletonList) bool {
   319  	return slices.EqualFunc(lhs, rhs, func(a, b *IdentitySkeleton) bool {
   320  		return a.EqualTo(b)
   321  	})
   322  }
   323  
   324  // Exists takes a previously sorted Identity list and searches it for the target
   325  // identity by its NodeID.
   326  // CAUTION:
   327  //   - Other identity fields are not compared.
   328  //   - The identity list MUST be sorted prior to calling this method.
   329  func (il GenericIdentityList[T]) Exists(target *T) bool {
   330  	return il.IdentifierExists((*target).GetNodeID())
   331  }
   332  
   333  // IdentifierExists takes a previously sorted Identity list and searches it for the target value
   334  // target:  value to search for
   335  // CAUTION:  The identity list MUST be sorted prior to calling this method
   336  func (il GenericIdentityList[T]) IdentifierExists(target Identifier) bool {
   337  	_, ok := slices.BinarySearchFunc(il, target, func(a *T, b Identifier) int {
   338  		lhs := (*a).GetNodeID()
   339  		return bytes.Compare(lhs[:], b[:])
   340  	})
   341  	return ok
   342  }
   343  
   344  // GetIndex returns the index of the identifier in the IdentityList and true
   345  // if the identifier is found.
   346  func (il GenericIdentityList[T]) GetIndex(target Identifier) (uint, bool) {
   347  	i := slices.IndexFunc(il, func(a *T) bool {
   348  		return (*a).GetNodeID() == target
   349  	})
   350  	if i == -1 {
   351  		return 0, false
   352  	}
   353  	return uint(i), true
   354  }
   355  
   356  // ToSkeleton converts the identity list to a list of identity skeletons.
   357  func (il GenericIdentityList[T]) ToSkeleton() IdentitySkeletonList {
   358  	skeletons := make(IdentitySkeletonList, len(il))
   359  	for i, id := range il {
   360  		v := (*id).GetSkeleton()
   361  		skeletons[i] = &v
   362  	}
   363  	return skeletons
   364  }