github.com/onflow/flow-go@v0.33.17/model/flow/identity.go (about) 1 package flow 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io" 7 "math" 8 "regexp" 9 "strconv" 10 11 "golang.org/x/exp/slices" 12 13 "github.com/ethereum/go-ethereum/rlp" 14 "github.com/fxamacker/cbor/v2" 15 "github.com/pkg/errors" 16 "github.com/vmihailenco/msgpack" 17 18 "github.com/onflow/flow-go/crypto" 19 "github.com/onflow/flow-go/utils/rand" 20 ) 21 22 // DefaultInitialWeight is the default initial weight for a node identity. 23 // It is equal to the default initial weight in the FlowIDTableStaking smart contract. 24 const DefaultInitialWeight = 100 25 26 // rxid is the regex for parsing node identity entries. 27 var rxid = regexp.MustCompile(`^(collection|consensus|execution|verification|access)-([0-9a-fA-F]{64})@([\w\d]+|[\w\d][\w\d\-]*[\w\d](?:\.*[\w\d][\w\d\-]*[\w\d])*|[\w\d][\w\d\-]*[\w\d])(:[\d]+)?=(\d{1,20})$`) 28 29 // Identity represents the public identity of one network participant (node). 30 type Identity struct { 31 // NodeID uniquely identifies a particular node. A node's ID is fixed for 32 // the duration of that node's participation in the network. 33 NodeID Identifier 34 // Address is the network address where the node can be reached. 35 Address string 36 // Role is the node's role in the network and defines its abilities and 37 // responsibilities. 38 Role Role 39 // Weight represents the node's authority to perform certain tasks relative 40 // to other nodes. For example, in the consensus committee, the node's weight 41 // represents the weight assigned to its votes. 42 // 43 // A node's weight is distinct from its stake. Stake represents the quantity 44 // of FLOW tokens held by the network in escrow during the course of the node's 45 // participation in the network. The stake is strictly managed by the service 46 // account smart contracts. 47 // 48 // Nodes which are registered to join at the next epoch will appear in the 49 // identity table but are considered to have zero weight up until their first 50 // epoch begins. Likewise nodes which were registered in the previous epoch 51 // but have left at the most recent epoch boundary will appear in the identity 52 // table with zero weight. 53 Weight uint64 54 // Ejected represents whether a node has been permanently removed from the 55 // network. A node may be ejected for either: 56 // * committing one protocol felony 57 // * committing a series of protocol misdemeanours 58 Ejected bool 59 StakingPubKey crypto.PublicKey 60 NetworkPubKey crypto.PublicKey 61 } 62 63 func (id *Identity) Equals(other *Identity) bool { 64 if other == nil { 65 return false 66 } 67 return id.NodeID == other.NodeID && 68 id.Address == other.Address && 69 id.Role == other.Role && 70 id.Weight == other.Weight && 71 id.Ejected == other.Ejected && 72 id.StakingPubKey.Equals(other.StakingPubKey) && 73 id.NetworkPubKey.Equals(other.NetworkPubKey) 74 } 75 76 // ParseIdentity parses a string representation of an identity. 77 func ParseIdentity(identity string) (*Identity, error) { 78 79 // use the regex to match the four parts of an identity 80 matches := rxid.FindStringSubmatch(identity) 81 if len(matches) != 6 { 82 return nil, errors.New("invalid identity string format") 83 } 84 85 // none of these will error as they are checked by the regex 86 var nodeID Identifier 87 nodeID, err := HexStringToIdentifier(matches[2]) 88 if err != nil { 89 return nil, err 90 } 91 address := matches[3] + matches[4] 92 role, _ := ParseRole(matches[1]) 93 weight, _ := strconv.ParseUint(matches[5], 10, 64) 94 95 // create the identity 96 iy := Identity{ 97 NodeID: nodeID, 98 Address: address, 99 Role: role, 100 Weight: weight, 101 } 102 103 return &iy, nil 104 } 105 106 // String returns a string representation of the identity. 107 func (iy Identity) String() string { 108 return fmt.Sprintf("%s-%s@%s=%d", iy.Role, iy.NodeID.String(), iy.Address, iy.Weight) 109 } 110 111 // ID returns a unique, persistent identifier for the identity. 112 // CAUTION: the ID may be chosen by a node operator, so long as it is unique. 113 func (iy Identity) ID() Identifier { 114 return iy.NodeID 115 } 116 117 // Checksum returns a checksum for the identity including mutable attributes. 118 func (iy Identity) Checksum() Identifier { 119 return MakeID(iy) 120 } 121 122 type encodableIdentity struct { 123 NodeID Identifier 124 Address string `json:",omitempty"` 125 Role Role 126 Weight uint64 127 StakingPubKey []byte 128 NetworkPubKey []byte 129 } 130 131 // decodableIdentity provides backward-compatible decoding of old models 132 // which use the Stake field in place of Weight. 133 type decodableIdentity struct { 134 encodableIdentity 135 // Stake previously was used in place of the Weight field. 136 // Deprecated: supported in decoding for backward-compatibility 137 Stake uint64 138 } 139 140 func encodableFromIdentity(iy Identity) (encodableIdentity, error) { 141 ie := encodableIdentity{iy.NodeID, iy.Address, iy.Role, iy.Weight, nil, nil} 142 if iy.StakingPubKey != nil { 143 ie.StakingPubKey = iy.StakingPubKey.Encode() 144 } 145 if iy.NetworkPubKey != nil { 146 ie.NetworkPubKey = iy.NetworkPubKey.Encode() 147 } 148 return ie, nil 149 } 150 151 func (iy Identity) MarshalJSON() ([]byte, error) { 152 encodable, err := encodableFromIdentity(iy) 153 if err != nil { 154 return nil, fmt.Errorf("could not convert identity to encodable: %w", err) 155 } 156 157 data, err := json.Marshal(encodable) 158 if err != nil { 159 return nil, fmt.Errorf("could not encode json: %w", err) 160 } 161 return data, nil 162 } 163 164 func (iy Identity) MarshalCBOR() ([]byte, error) { 165 encodable, err := encodableFromIdentity(iy) 166 if err != nil { 167 return nil, fmt.Errorf("could not convert identity to encodable: %w", err) 168 } 169 data, err := cbor.Marshal(encodable) 170 if err != nil { 171 return nil, fmt.Errorf("could not encode cbor: %w", err) 172 } 173 return data, nil 174 } 175 176 func (iy Identity) MarshalMsgpack() ([]byte, error) { 177 encodable, err := encodableFromIdentity(iy) 178 if err != nil { 179 return nil, fmt.Errorf("could not convert to encodable: %w", err) 180 } 181 data, err := msgpack.Marshal(encodable) 182 if err != nil { 183 return nil, fmt.Errorf("could not encode msgpack: %w", err) 184 } 185 return data, nil 186 } 187 188 func (iy Identity) EncodeRLP(w io.Writer) error { 189 encodable, err := encodableFromIdentity(iy) 190 if err != nil { 191 return fmt.Errorf("could not convert to encodable: %w", err) 192 } 193 err = rlp.Encode(w, encodable) 194 if err != nil { 195 return fmt.Errorf("could not encode rlp: %w", err) 196 } 197 return nil 198 } 199 200 func identityFromEncodable(ie encodableIdentity, identity *Identity) error { 201 identity.NodeID = ie.NodeID 202 identity.Address = ie.Address 203 identity.Role = ie.Role 204 identity.Weight = ie.Weight 205 var err error 206 if ie.StakingPubKey != nil { 207 if identity.StakingPubKey, err = crypto.DecodePublicKey(crypto.BLSBLS12381, ie.StakingPubKey); err != nil { 208 return fmt.Errorf("could not decode staking key: %w", err) 209 } 210 } 211 if ie.NetworkPubKey != nil { 212 if identity.NetworkPubKey, err = crypto.DecodePublicKey(crypto.ECDSAP256, ie.NetworkPubKey); err != nil { 213 return fmt.Errorf("could not decode network key: %w", err) 214 } 215 } 216 return nil 217 } 218 219 func (iy *Identity) UnmarshalJSON(b []byte) error { 220 var decodable decodableIdentity 221 err := json.Unmarshal(b, &decodable) 222 if err != nil { 223 return fmt.Errorf("could not decode json: %w", err) 224 } 225 // compat: translate Stake fields to Weight 226 if decodable.Stake != 0 { 227 if decodable.Weight != 0 { 228 return fmt.Errorf("invalid identity with both Stake and Weight fields") 229 } 230 decodable.Weight = decodable.Stake 231 } 232 err = identityFromEncodable(decodable.encodableIdentity, iy) 233 if err != nil { 234 return fmt.Errorf("could not convert from encodable json: %w", err) 235 } 236 return nil 237 } 238 239 func (iy *Identity) UnmarshalCBOR(b []byte) error { 240 var encodable encodableIdentity 241 err := cbor.Unmarshal(b, &encodable) 242 if err != nil { 243 return fmt.Errorf("could not decode json: %w", err) 244 } 245 err = identityFromEncodable(encodable, iy) 246 if err != nil { 247 return fmt.Errorf("could not convert from encodable cbor: %w", err) 248 } 249 return nil 250 } 251 252 func (iy *Identity) UnmarshalMsgpack(b []byte) error { 253 var encodable encodableIdentity 254 err := msgpack.Unmarshal(b, &encodable) 255 if err != nil { 256 return fmt.Errorf("could not decode json: %w", err) 257 } 258 err = identityFromEncodable(encodable, iy) 259 if err != nil { 260 return fmt.Errorf("could not convert from encodable msgpack: %w", err) 261 } 262 return nil 263 } 264 265 func (iy *Identity) EqualTo(other *Identity) bool { 266 if iy.NodeID != other.NodeID { 267 return false 268 } 269 if iy.Address != other.Address { 270 return false 271 } 272 if iy.Role != other.Role { 273 return false 274 } 275 if iy.Weight != other.Weight { 276 return false 277 } 278 if iy.Ejected != other.Ejected { 279 return false 280 } 281 if (iy.StakingPubKey != nil && other.StakingPubKey == nil) || 282 (iy.StakingPubKey == nil && other.StakingPubKey != nil) { 283 return false 284 } 285 if iy.StakingPubKey != nil && !iy.StakingPubKey.Equals(other.StakingPubKey) { 286 return false 287 } 288 289 if (iy.NetworkPubKey != nil && other.NetworkPubKey == nil) || 290 (iy.NetworkPubKey == nil && other.NetworkPubKey != nil) { 291 return false 292 } 293 if iy.NetworkPubKey != nil && !iy.NetworkPubKey.Equals(other.NetworkPubKey) { 294 return false 295 } 296 297 return true 298 } 299 300 // IdentityFilter is a filter on identities. 301 type IdentityFilter func(*Identity) bool 302 303 // IdentityOrder is an order function for identities. 304 // 305 // It defines a strict weak ordering between identities. 306 // It returns a negative number if the first identity is "strictly less" than the second, 307 // a positive number if the second identity is "strictly less" than the first, 308 // and zero if the two identities are equal. 309 // 310 // `IdentityOrder` can be used to sort identities with 311 // https://pkg.go.dev/golang.org/x/exp/slices#SortFunc. 312 type IdentityOrder func(*Identity, *Identity) int 313 314 // IdentityMapFunc is a modifier function for map operations for identities. 315 // Identities are COPIED from the source slice. 316 type IdentityMapFunc func(Identity) Identity 317 318 // IdentityList is a list of nodes. 319 type IdentityList []*Identity 320 321 // Filter will apply a filter to the identity list. 322 func (il IdentityList) Filter(filter IdentityFilter) IdentityList { 323 var dup IdentityList 324 IDLoop: 325 for _, identity := range il { 326 if !filter(identity) { 327 continue IDLoop 328 } 329 dup = append(dup, identity) 330 } 331 return dup 332 } 333 334 // Map returns a new identity list with the map function f applied to a copy of 335 // each identity. 336 // 337 // CAUTION: this relies on structure copy semantics. Map functions that modify 338 // an object referenced by the input Identity structure will modify identities 339 // in the source slice as well. 340 func (il IdentityList) Map(f IdentityMapFunc) IdentityList { 341 dup := make(IdentityList, 0, len(il)) 342 for _, identity := range il { 343 next := f(*identity) 344 dup = append(dup, &next) 345 } 346 return dup 347 } 348 349 // Copy returns a copy of the receiver. The resulting slice uses a different 350 // backing array, meaning appends and insert operations on either slice are 351 // guaranteed to only affect that slice. 352 // 353 // Copy should be used when modifying an existing identity list by either 354 // appending new elements, re-ordering, or inserting new elements in an 355 // existing index. 356 func (il IdentityList) Copy() IdentityList { 357 dup := make(IdentityList, 0, len(il)) 358 359 lenList := len(il) 360 361 // performance tests show this is faster than 'range' 362 for i := 0; i < lenList; i++ { 363 // copy the object 364 next := *(il[i]) 365 dup = append(dup, &next) 366 } 367 return dup 368 } 369 370 // Selector returns an identity filter function that selects only identities 371 // within this identity list. 372 func (il IdentityList) Selector() IdentityFilter { 373 374 lookup := il.Lookup() 375 return func(identity *Identity) bool { 376 _, exists := lookup[identity.NodeID] 377 return exists 378 } 379 } 380 381 func (il IdentityList) Lookup() map[Identifier]*Identity { 382 lookup := make(map[Identifier]*Identity, len(il)) 383 for _, identity := range il { 384 lookup[identity.NodeID] = identity 385 } 386 return lookup 387 } 388 389 // Sort will sort the list using the given ordering. This is 390 // not recommended for performance. Expand the 'less' function 391 // in place for best performance, and don't use this function. 392 func (il IdentityList) Sort(less IdentityOrder) IdentityList { 393 dup := il.Copy() 394 slices.SortFunc(dup, less) 395 return dup 396 } 397 398 // NodeIDs returns the NodeIDs of the nodes in the list. 399 func (il IdentityList) NodeIDs() IdentifierList { 400 nodeIDs := make([]Identifier, 0, len(il)) 401 for _, id := range il { 402 nodeIDs = append(nodeIDs, id.NodeID) 403 } 404 return nodeIDs 405 } 406 407 // PublicStakingKeys returns a list with the public staking keys (order preserving). 408 func (il IdentityList) PublicStakingKeys() []crypto.PublicKey { 409 pks := make([]crypto.PublicKey, 0, len(il)) 410 for _, id := range il { 411 pks = append(pks, id.StakingPubKey) 412 } 413 return pks 414 } 415 416 // ID uniquely identifies a list of identities, by node ID. This can be used 417 // to perpetually identify a group of nodes, even if mutable fields of some nodes 418 // are changed, as node IDs are immutable. 419 // CAUTION: 420 // - An IdentityList's ID is a cryptographic commitment to only node IDs. A node operator 421 // can freely choose the ID for their node. There is no relationship whatsoever between 422 // a node's ID and keys. 423 // - To generate a cryptographic commitment for the full IdentityList, use method `Checksum()`. 424 // - The outputs of `IdentityList.ID()` and `IdentityList.Checksum()` are both order-sensitive. 425 // Therefore, the `IdentityList` must be in canonical order, unless explicitly specified 426 // otherwise by the protocol. 427 func (il IdentityList) ID() Identifier { 428 return il.NodeIDs().ID() 429 } 430 431 // Checksum generates a cryptographic commitment to the full IdentityList, including mutable fields. 432 // The checksum for the same group of identities (by NodeID) may change from block to block. 433 func (il IdentityList) Checksum() Identifier { 434 return MakeID(il) 435 } 436 437 // TotalWeight returns the total weight of all given identities. 438 func (il IdentityList) TotalWeight() uint64 { 439 var total uint64 440 for _, identity := range il { 441 total += identity.Weight 442 } 443 return total 444 } 445 446 // Count returns the count of identities. 447 func (il IdentityList) Count() uint { 448 return uint(len(il)) 449 } 450 451 // ByIndex returns the node at the given index. 452 func (il IdentityList) ByIndex(index uint) (*Identity, bool) { 453 if index >= uint(len(il)) { 454 return nil, false 455 } 456 return il[int(index)], true 457 } 458 459 // ByNodeID gets a node from the list by node ID. 460 func (il IdentityList) ByNodeID(nodeID Identifier) (*Identity, bool) { 461 for _, identity := range il { 462 if identity.NodeID == nodeID { 463 return identity, true 464 } 465 } 466 return nil, false 467 } 468 469 // ByNetworkingKey gets a node from the list by network public key. 470 func (il IdentityList) ByNetworkingKey(key crypto.PublicKey) (*Identity, bool) { 471 for _, identity := range il { 472 if identity.NetworkPubKey.Equals(key) { 473 return identity, true 474 } 475 } 476 return nil, false 477 } 478 479 // Sample returns non-deterministic random sample from the `IdentityList` 480 func (il IdentityList) Sample(size uint) (IdentityList, error) { 481 n := uint(len(il)) 482 dup := make([]*Identity, 0, n) 483 dup = append(dup, il...) 484 if n < size { 485 size = n 486 } 487 swap := func(i, j uint) { 488 dup[i], dup[j] = dup[j], dup[i] 489 } 490 err := rand.Samples(n, size, swap) 491 if err != nil { 492 return nil, fmt.Errorf("failed to sample identity list: %w", err) 493 } 494 return dup[:size], nil 495 } 496 497 // Shuffle randomly shuffles the identity list (non-deterministic), 498 // and returns the shuffled list without modifying the receiver. 499 func (il IdentityList) Shuffle() (IdentityList, error) { 500 return il.Sample(uint(len(il))) 501 } 502 503 // SamplePct returns a random sample from the receiver identity list. The 504 // sample contains `pct` percentage of the list. The sample is rounded up 505 // if `pct>0`, so this will always select at least one identity. 506 // 507 // NOTE: The input must be between 0-1. 508 func (il IdentityList) SamplePct(pct float64) (IdentityList, error) { 509 if pct <= 0 { 510 return IdentityList{}, nil 511 } 512 513 count := float64(il.Count()) * pct 514 size := uint(math.Round(count)) 515 // ensure we always select at least 1, for non-zero input 516 if size == 0 { 517 size = 1 518 } 519 520 return il.Sample(size) 521 } 522 523 // Union returns a new identity list containing every identity that occurs in 524 // either `il`, or `other`, or both. There are no duplicates in the output, 525 // where duplicates are identities with the same node ID. 526 // The returned IdentityList is sorted canonically. 527 func (il IdentityList) Union(other IdentityList) IdentityList { 528 maxLen := len(il) + len(other) 529 530 union := make(IdentityList, 0, maxLen) 531 set := make(map[Identifier]struct{}, maxLen) 532 533 for _, list := range []IdentityList{il, other} { 534 for _, id := range list { 535 if _, isDuplicate := set[id.NodeID]; !isDuplicate { 536 set[id.NodeID] = struct{}{} 537 union = append(union, id) 538 } 539 } 540 } 541 542 slices.SortFunc(union, Canonical) 543 return union 544 } 545 546 // EqualTo checks if the other list if the same, that it contains the same elements 547 // in the same order 548 func (il IdentityList) EqualTo(other IdentityList) bool { 549 return slices.EqualFunc(il, other, func(a, b *Identity) bool { 550 return a.EqualTo(b) 551 }) 552 } 553 554 // Exists takes a previously sorted Identity list and searches it for the target value 555 // This code is optimized, so the coding style will be different 556 // target: value to search for 557 // CAUTION: The identity list MUST be sorted prior to calling this method 558 func (il IdentityList) Exists(target *Identity) bool { 559 return il.IdentifierExists(target.NodeID) 560 } 561 562 // IdentifierExists takes a previously sorted Identity list and searches it for the target value 563 // target: value to search for 564 // CAUTION: The identity list MUST be sorted prior to calling this method 565 func (il IdentityList) IdentifierExists(target Identifier) bool { 566 _, ok := slices.BinarySearchFunc(il, &Identity{NodeID: target}, Canonical) 567 return ok 568 } 569 570 // GetIndex returns the index of the identifier in the IdentityList and true 571 // if the identifier is found. 572 func (il IdentityList) GetIndex(target Identifier) (uint, bool) { 573 i := slices.IndexFunc(il, func(a *Identity) bool { 574 return a.NodeID == target 575 }) 576 if i == -1 { 577 return 0, false 578 } 579 return uint(i), true 580 }