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  }