github.com/aergoio/aergo@v1.3.1/consensus/impl/dpos/bp/cluster.go (about) 1 /** 2 * @file 3 * @copyright defined in aergo/LICENSE.txt 4 */ 5 6 package bp 7 8 import ( 9 "encoding/json" 10 "errors" 11 "fmt" 12 "math" 13 "strconv" 14 "sync" 15 16 "github.com/aergoio/aergo-lib/log" 17 "github.com/aergoio/aergo/consensus" 18 "github.com/aergoio/aergo/contract/system" 19 "github.com/aergoio/aergo/internal/common" 20 "github.com/aergoio/aergo/state" 21 "github.com/aergoio/aergo/types" 22 "github.com/davecgh/go-spew/spew" 23 ) 24 25 const ( 26 max = 100 27 28 // New BPs are elected every maxBpLimit blocks. 29 electionPeriod = types.BlockNo(max) 30 ) 31 32 var ( 33 logger = log.NewLogger("bp") 34 errNoBP = errors.New("no block producers found in the block chain") 35 36 genesisBpList []string 37 ) 38 39 type errBpSize struct { 40 required uint16 41 given uint16 42 } 43 44 func (e errBpSize) Error() string { 45 return fmt.Sprintf("insufficient or redundant block producers - %v (required - %v)", e.given, e.required) 46 } 47 48 // Max returns the maximum number of active block producers. 49 func Max() uint16 { 50 return max 51 } 52 53 // ClusterMember is an interface which corresponds to BP member udpate. 54 type ClusterMember interface { 55 Size() uint16 56 Update(ids []string) error 57 } 58 59 // Cluster represents a cluster of block producers. 60 type Cluster struct { 61 sync.RWMutex 62 size uint16 63 member map[Index]*blockProducer 64 index map[types.PeerID]Index 65 66 cdb consensus.ChainDB 67 } 68 69 // blockProducer represents one member in the block producer cluster. 70 type blockProducer struct { 71 id types.PeerID 72 } 73 74 // NewCluster returns a new bp.Cluster. 75 func NewCluster(cdb consensus.ChainDB) (*Cluster, error) { 76 c := &Cluster{cdb: cdb} 77 78 if err := c.init(); err != nil { 79 return nil, err 80 } 81 82 return c, nil 83 } 84 85 func newBlockProducer(id types.PeerID) *blockProducer { 86 return &blockProducer{id: id} 87 } 88 89 func (c *Cluster) init() error { 90 if c.cdb == nil { 91 return errNoBP 92 } 93 94 if genesisBpList = c.genesisBpList(); len(genesisBpList) == 0 { 95 return errNoBP 96 } 97 98 // The total BP count is determined by the genesis info and afterwards it 99 // remains the same. 100 c.size = uint16(len(genesisBpList)) 101 102 // The boot time BP member loading is later performed along with DPoS 103 // status initilization. 104 105 return nil 106 } 107 108 func bootstrapHeight() types.BlockNo { 109 return getElectionPeriod() * 3 110 } 111 112 func (c *Cluster) genesisBpList() []string { 113 genesis := c.cdb.GetGenesisInfo() 114 if genesis != nil { 115 logger.Debug().Str("genesis", spew.Sdump(genesis)).Msg("genesis info loaded") 116 // Prefer BPs from the GenesisInfo. Overwrite. 117 if len(genesis.BPs) > 0 { 118 logger.Debug().Msg("use BPs from the genesis info") 119 for i, bp := range genesis.BPs { 120 logger.Debug().Int("no", i).Str("ID", bp).Msg("Genesis BP") 121 } 122 return genesis.BPs 123 } 124 } 125 return nil 126 } 127 128 // BPs returns BP information about each BP in JSON. 129 func (c *Cluster) BPs() []string { 130 c.RLock() 131 defer c.RUnlock() 132 133 if c == nil || c.getSize() == 0 || len(c.member) != int(c.getSize()) { 134 return nil 135 } 136 bps := make([]string, c.getSize()) 137 for i, bp := range c.member { 138 p := &struct { 139 Index string 140 PeerID string 141 }{ 142 Index: strconv.FormatUint(uint64(i), 10), 143 PeerID: bp.id.Pretty(), 144 } 145 146 m, err := json.Marshal(p) 147 if err != nil { 148 bps = nil 149 break 150 } 151 bps[int(i)] = string(m) 152 } 153 return bps 154 } 155 156 // Update updates old cluster index by using ids. 157 func (c *Cluster) Update(ids []string) error { 158 c.Lock() 159 defer c.Unlock() 160 161 bpMember := make(map[Index]*blockProducer) 162 bpIndex := make(map[types.PeerID]Index) 163 164 for i, id := range ids { 165 bpID, err := types.IDB58Decode(id) 166 if err != nil { 167 return fmt.Errorf("invalid node ID[%d]: %s", i, err.Error()) 168 } 169 170 var index Index 171 if index, err = newIndex(i); err != nil { 172 return err 173 } 174 175 bpMember[index] = newBlockProducer(bpID) 176 bpIndex[bpID] = index 177 } 178 179 c.size = uint16(len(bpMember)) 180 c.member = bpMember 181 c.index = bpIndex 182 183 logger.Debug().Msgf("BP list updated. member: %v", ids) 184 185 return nil 186 } 187 188 // Size returns c.size. 189 func (c *Cluster) Size() uint16 { 190 c.RLock() 191 defer c.RUnlock() 192 return c.getSize() 193 } 194 195 func (c *Cluster) getSize() uint16 { 196 return c.size 197 } 198 199 // Index is a type for a block producer index. 200 type Index uint16 201 202 // indexNil is the nil value for BpIndex type 203 const ( 204 indexNil = Index(math.MaxUint16) 205 indexMax = indexNil - 1 206 ) 207 208 func newIndex(i int) (Index, error) { 209 if i > int(indexMax) { 210 return indexNil, fmt.Errorf("BP index [%v] is too big", i) 211 } 212 return Index(i), nil 213 } 214 215 // IsNil reports whether idx is nil or not. 216 func (idx Index) IsNil() bool { 217 return idx == indexNil 218 } 219 220 // BpIndex2ID returns the ID correspinding to idx. 221 func (c *Cluster) BpIndex2ID(bpIdx Index) (types.PeerID, bool) { 222 c.Lock() 223 defer c.Unlock() 224 225 if bp, exist := c.member[bpIdx]; exist { 226 return bp.id, exist 227 } 228 return types.PeerID(""), false 229 } 230 231 // BpID2Index returns the index corresponding to id. 232 func (c *Cluster) BpID2Index(id types.PeerID) Index { 233 c.Lock() 234 defer c.Unlock() 235 idx, exist := c.index[id] 236 if exist { 237 return idx 238 } 239 240 return indexNil 241 } 242 243 // Has reports whether c includes id or not 244 func (c *Cluster) Has(id types.PeerID) bool { 245 c.Lock() 246 defer c.Unlock() 247 _, exist := c.index[id] 248 return exist 249 } 250 251 // Snapshot represents the set of the elected BP at refBlockNo. 252 type Snapshot struct { 253 RefBlockNo types.BlockNo 254 List []string 255 } 256 257 // NewSnapshot returns a Snapshot corresponding to blockNo and period. 258 func NewSnapshot(blockNo types.BlockNo, bps []string) (*Snapshot, error) { 259 if !isSnapPeriod(blockNo) { 260 return nil, fmt.Errorf("%v is not inconsistent with period %v", blockNo, getElectionPeriod()) 261 } 262 return &Snapshot{RefBlockNo: blockNo, List: bps}, nil 263 } 264 265 func snapBlockNo(blockNo types.BlockNo) types.BlockNo { 266 if blockNo < bootstrapHeight() { 267 return 0 268 } 269 return (blockNo/getElectionPeriod() - 1) * getElectionPeriod() 270 } 271 272 func isSnapPeriod(blockNo types.BlockNo) bool { 273 // The current snapshot period is the total BP count. 274 return blockNo%getElectionPeriod() == 0 275 } 276 277 // Key returns the properly prefixed key corresponding to s. 278 func (s *Snapshot) Key() []byte { 279 return buildKey(s.RefBlockNo) 280 } 281 282 func buildKey(blockNo types.BlockNo) []byte { 283 const bpListPrefix = "dpos.BpList" 284 285 return []byte(fmt.Sprintf("%v.%v", bpListPrefix, blockNo)) 286 } 287 288 // Value returns s.list. 289 func (s *Snapshot) Value() []byte { 290 b, err := common.GobEncode(s.List) 291 if err != nil { 292 logger.Debug().Err(err).Msg("BP list encoding failed") 293 return nil 294 } 295 return b 296 } 297 298 const ( 299 opNil = iota 300 opAdd 301 opDel 302 ) 303 304 type journal struct { 305 op int 306 blockNo types.BlockNo 307 } 308 309 // Snapshots is a map from block no to *Snapshot. 310 type Snapshots struct { 311 snaps map[types.BlockNo]*Snapshot 312 maxRefBlockNo types.BlockNo 313 cm ClusterMember 314 cdb consensus.ChainDB 315 sdb *state.ChainStateDB 316 } 317 318 // NewSnapshots returns a new Snapshots. 319 func NewSnapshots(c ClusterMember, cdb consensus.ChainDB, sdb *state.ChainStateDB) *Snapshots { 320 snap := &Snapshots{ 321 snaps: make(map[types.BlockNo]*Snapshot), 322 cm: c, 323 cdb: cdb, 324 sdb: sdb, 325 } 326 327 // To avoid a unit test failure. 328 if cdb == nil { 329 return snap 330 } 331 332 // Initialize the BP cluster members. 333 if block, err := cdb.GetBestBlock(); err == nil { 334 snap.UpdateCluster(block.BlockNo()) 335 } else { 336 panic(err.Error()) 337 } 338 339 return snap 340 } 341 342 // NeedToRefresh reports whether blockNo corresponds to a BP regime change 343 // point. 344 func (sn *Snapshots) NeedToRefresh(blockNo types.BlockNo) bool { 345 return blockNo%getElectionPeriod() == 0 346 } 347 348 // AddSnapshot add a new BP list corresponding to refBlockNO to sn. 349 func (sn *Snapshots) AddSnapshot(refBlockNo types.BlockNo) ([]string, error) { 350 // Reorganization!!! 351 if sn.maxRefBlockNo > refBlockNo { 352 sn.reset() 353 } 354 355 // Add BP list every 'sn.bpCount'rd block. 356 if sn.sdb == nil || !isSnapPeriod(refBlockNo) || refBlockNo == 0 { 357 return nil, nil 358 } 359 360 var ( 361 bps []string 362 err error 363 ) 364 365 if bps, err = sn.gatherRankers(); err != nil { 366 return nil, err 367 } 368 369 if err := sn.add(refBlockNo, bps); err != nil { 370 return nil, err 371 } 372 373 if sn.NeedToRefresh(refBlockNo) { 374 sn.UpdateCluster(refBlockNo) 375 } 376 377 sn.gc(refBlockNo) 378 379 return bps, nil 380 } 381 382 func (sn *Snapshots) gatherRankers() ([]string, error) { 383 return system.GetRankers(sn.sdb) 384 } 385 386 // UpdateCluster updates the current BP list by the ones corresponding to 387 // blockNo. 388 func (sn *Snapshots) UpdateCluster(blockNo types.BlockNo) { 389 var ( 390 err error 391 s []string 392 ) 393 394 if s, err = sn.getCurrentCluster(blockNo); err == nil { 395 logger.Debug().Uint64("cur block no", blockNo).Msg("get BP list snapshot") 396 err = sn.cm.Update(s) 397 } 398 399 if err != nil { 400 logger.Debug().Err(err).Msg("skip BP member update") 401 } 402 } 403 404 func (sn *Snapshots) reset() { 405 sn.snaps = make(map[types.BlockNo]*Snapshot) 406 } 407 408 // add adds a new BP snapshot to snap. 409 func (sn *Snapshots) add(refBlockNo types.BlockNo, bps []string) error { 410 var ( 411 s *Snapshot 412 err error 413 ) 414 415 if s, err = NewSnapshot(refBlockNo, bps); err != nil { 416 return err 417 } 418 419 sn.snaps[refBlockNo] = s 420 421 logger.Debug().Uint64("ref block no", refBlockNo).Msgf("BP snapshot added: %v", bps) 422 423 return nil 424 } 425 426 // del removes a snapshot corresponding to refBlockNo from sn.snaps. 427 func (sn *Snapshots) del(refBlockNo types.BlockNo) error { 428 if _, exist := sn.snaps[refBlockNo]; !exist { 429 logger.Debug().Uint64("ref block no", refBlockNo).Msg("no such an entry in BP snapshots. ignored.") 430 return nil 431 } 432 433 delete(sn.snaps, refBlockNo) 434 435 logger.Debug().Uint64("block no", refBlockNo).Int("len", len(sn.snaps)).Msg("BP snaphost removed") 436 437 return nil 438 } 439 440 // gc remove all the snapshots less than blockNo 441 func (sn *Snapshots) gc(blockNo types.BlockNo) { 442 gcPeriod := sn.gcPeriod() 443 444 var gcBlockNo types.BlockNo 445 if blockNo > gcPeriod { 446 gcBlockNo = blockNo - gcPeriod 447 } 448 449 for h := range sn.snaps { 450 if h < gcBlockNo { 451 sn.del(h) 452 } 453 } 454 } 455 456 func getElectionPeriod() types.BlockNo { 457 return electionPeriod 458 } 459 460 func (sn Snapshots) period() types.BlockNo { 461 return getElectionPeriod() 462 } 463 464 func (sn Snapshots) gcPeriod() types.BlockNo { 465 return 2 * sn.period() 466 } 467 468 // getCurrentCluster returns the BP snapshot corresponding to blockNo. 469 func (sn *Snapshots) getCurrentCluster(blockNo types.BlockNo) ([]string, error) { 470 refBlockNo := snapBlockNo(blockNo) 471 if refBlockNo == 0 { 472 return genesisBpList, nil 473 } 474 475 if s, exist := sn.snaps[refBlockNo]; exist { 476 return s.List, nil 477 } 478 479 return sn.loadClusterSnapshot(blockNo) 480 } 481 482 func (sn *Snapshots) loadClusterSnapshot(blockNo types.BlockNo) ([]string, error) { 483 var ( 484 block *types.Block 485 err error 486 ) 487 488 block, err = sn.cdb.GetBlockByNo(snapBlockNo(blockNo)) 489 if err != nil { 490 return nil, err 491 } 492 493 stateDB := sn.sdb.OpenNewStateDB(block.GetHeader().GetBlocksRootHash()) 494 495 return system.GetRankers(stateDB) 496 }