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 }