github.com/aergoio/aergo@v1.3.1/consensus/impl/dpos/dpos.go (about)

     1  /**
     2   *  @file
     3   *  @copyright defined in aergo/LICENSE.txt
     4   */
     5  
     6  package dpos
     7  
     8  import (
     9  	"encoding/json"
    10  	"fmt"
    11  	"time"
    12  
    13  	"github.com/aergoio/aergo/p2p/p2pkey"
    14  
    15  	"github.com/aergoio/aergo-lib/log"
    16  	"github.com/aergoio/aergo/config"
    17  	"github.com/aergoio/aergo/consensus"
    18  	"github.com/aergoio/aergo/consensus/impl/dpos/bp"
    19  	"github.com/aergoio/aergo/consensus/impl/dpos/slot"
    20  	"github.com/aergoio/aergo/pkg/component"
    21  	"github.com/aergoio/aergo/state"
    22  	"github.com/aergoio/aergo/types"
    23  )
    24  
    25  var (
    26  	logger = log.NewLogger("dpos")
    27  
    28  	// blockProducers is the number of block producers
    29  	blockProducers          uint16
    30  	majorityCount           uint16
    31  	initialBpElectionPeriod types.BlockNo
    32  
    33  	lastJob = &lastSlot{}
    34  )
    35  
    36  type lastSlot struct {
    37  	//	sync.Mutex
    38  	s *slot.Slot
    39  }
    40  
    41  func (l *lastSlot) get() *slot.Slot {
    42  	//	l.Lock()
    43  	//	defer l.Unlock()
    44  	return l.s
    45  }
    46  
    47  func (l *lastSlot) set(s *slot.Slot) {
    48  	//	l.Lock()
    49  	//	defer l.Unlock()
    50  	l.s = s
    51  }
    52  
    53  // DPoS is the main data structure of DPoS consensus
    54  type DPoS struct {
    55  	*Status
    56  	consensus.ChainDB
    57  	*component.ComponentHub
    58  	bpc  *bp.Cluster
    59  	bf   *BlockFactory
    60  	quit chan interface{}
    61  }
    62  
    63  // Status shows DPoS consensus's current status
    64  type bpInfo struct {
    65  	consensus.ChainDB
    66  	bestBlock *types.Block
    67  	slot      *slot.Slot
    68  }
    69  
    70  func (bi *bpInfo) updateBestBLock() *types.Block {
    71  	block, _ := bi.GetBestBlock()
    72  	if block != nil {
    73  		bi.bestBlock = block
    74  	}
    75  
    76  	return block
    77  }
    78  
    79  // GetName returns the name of the consensus.
    80  func GetName() string {
    81  	return consensus.ConsensusName[consensus.ConsensusDPOS]
    82  }
    83  
    84  // GetConstructor build and returns consensus.Constructor from New function.
    85  func GetConstructor(cfg *config.Config, hub *component.ComponentHub, cdb consensus.ChainDB,
    86  	sdb *state.ChainStateDB) consensus.Constructor {
    87  	return func() (consensus.Consensus, error) {
    88  		return New(cfg, hub, cdb, sdb)
    89  	}
    90  }
    91  
    92  // New returns a new DPos object
    93  func New(cfg *config.Config, hub *component.ComponentHub, cdb consensus.ChainDB,
    94  	sdb *state.ChainStateDB) (consensus.Consensus, error) {
    95  	bpc, err := bp.NewCluster(cdb)
    96  	if err != nil {
    97  		return nil, err
    98  	}
    99  
   100  	Init(bpc.Size())
   101  
   102  	quitC := make(chan interface{})
   103  
   104  	return &DPoS{
   105  		Status:       NewStatus(bpc, cdb, sdb, cfg.Blockchain.ForceResetHeight),
   106  		ComponentHub: hub,
   107  		ChainDB:      cdb,
   108  		bpc:          bpc,
   109  		bf:           NewBlockFactory(hub, sdb, quitC),
   110  		quit:         quitC,
   111  	}, nil
   112  }
   113  
   114  // Init initilizes the DPoS parameters.
   115  func Init(bpCount uint16) {
   116  	blockProducers = bpCount
   117  	majorityCount = blockProducers*2/3 + 1
   118  	// Collect voting for BPs during 10 rounds.
   119  	initialBpElectionPeriod = types.BlockNo(blockProducers) * 10
   120  	slot.Init(consensus.BlockIntervalSec)
   121  }
   122  
   123  func consensusBlockCount(bpCount uint16) uint16 {
   124  	return bpCount*2/3 + 1
   125  }
   126  
   127  // Ticker returns a time.Ticker for the main consensus loop.
   128  func (dpos *DPoS) Ticker() *time.Ticker {
   129  	return time.NewTicker(tickDuration())
   130  }
   131  
   132  func tickDuration() time.Duration {
   133  	return consensus.BlockInterval / 100
   134  }
   135  
   136  // QueueJob send a block triggering information to jq.
   137  func (dpos *DPoS) QueueJob(now time.Time, jq chan<- interface{}) {
   138  	bpi := dpos.getBpInfo(now)
   139  	if bpi != nil {
   140  		jq <- bpi
   141  		lastJob.set(bpi.slot)
   142  	}
   143  }
   144  
   145  // BlockFactory returns the BlockFactory interface in dpos.
   146  func (dpos *DPoS) BlockFactory() consensus.BlockFactory {
   147  	return dpos.bf
   148  }
   149  
   150  func (dpos *DPoS) GetType() consensus.ConsensusType {
   151  	return consensus.ConsensusDPOS
   152  }
   153  
   154  // IsTransactionValid checks the DPoS consensus level validity of a transaction
   155  func (dpos *DPoS) IsTransactionValid(tx *types.Tx) bool {
   156  	// TODO: put a transaction validity check code here.
   157  	return true
   158  }
   159  
   160  // QuitChan returns the channel from which consensus-related goroutines check when
   161  // shutdown is initiated.
   162  func (dpos *DPoS) QuitChan() chan interface{} {
   163  	return dpos.quit
   164  }
   165  
   166  func (dpos *DPoS) bpid() types.PeerID {
   167  	return p2pkey.NodeID()
   168  }
   169  
   170  // VerifyTimestamp checks the validity of the block timestamp.
   171  func (dpos *DPoS) VerifyTimestamp(block *types.Block) bool {
   172  
   173  	if ts := block.GetHeader().GetTimestamp(); slot.NewFromUnixNano(ts).IsFuture() {
   174  		logger.Error().Str("BP", block.BPID2Str()).Str("id", block.ID()).
   175  			Time("timestamp", time.Unix(0, ts)).Msg("block has a future timestamp")
   176  		return false
   177  	}
   178  
   179  	// Reject the blocks with no <= LIB since it cannot lead to a
   180  	// reorganization.
   181  	if dpos.Status != nil && block.BlockNo() <= dpos.libNo() {
   182  		logger.Error().Str("BP", block.BPID2Str()).Str("id", block.ID()).
   183  			Uint64("block no", block.BlockNo()).Uint64("lib no", dpos.libNo()).
   184  			Msg("too small block number (<= LIB number)")
   185  		return false
   186  	}
   187  
   188  	return true
   189  }
   190  
   191  // VerifySign reports the validity of the block signature.
   192  func (dpos *DPoS) VerifySign(block *types.Block) error {
   193  	valid, err := block.VerifySign()
   194  	if !valid || err != nil {
   195  		return &consensus.ErrorConsensus{Msg: "bad block signature", Err: err}
   196  	}
   197  	return nil
   198  }
   199  
   200  // IsBlockValid checks the DPoS consensus level validity of a block
   201  func (dpos *DPoS) IsBlockValid(block *types.Block, bestBlock *types.Block) error {
   202  	id, err := block.BPID()
   203  	if err != nil {
   204  		return &consensus.ErrorConsensus{Msg: "bad public key in block", Err: err}
   205  	}
   206  
   207  	idx := dpos.bpc.BpID2Index(id)
   208  	ns := block.GetHeader().GetTimestamp()
   209  	s := slot.NewFromUnixNano(ns)
   210  	// Check whether the BP ID is one of the current BP members and its
   211  	// corresponding BP index is consistent with the block timestamp.
   212  	if !s.IsFor(idx, dpos.bpc.Size()) {
   213  		return &consensus.ErrorConsensus{
   214  			Msg: fmt.Sprintf("BP %v (idx: %v) is not permitted for the time slot %v (%v)",
   215  				block.BPID2Str(), idx, time.Unix(0, ns), s.NextBpIndex(dpos.bpc.Size())),
   216  		}
   217  	}
   218  
   219  	return nil
   220  }
   221  
   222  func (dpos *DPoS) bpIdx() bp.Index {
   223  	return dpos.bpc.BpID2Index(dpos.bpid())
   224  }
   225  
   226  func (dpos *DPoS) getBpInfo(now time.Time) *bpInfo {
   227  	s := slot.Time(now)
   228  
   229  	if !s.IsFor(dpos.bpIdx(), dpos.bpc.Size()) {
   230  		return nil
   231  	}
   232  
   233  	// already queued slot.
   234  	if slot.Equal(s, lastJob.get()) {
   235  		return nil
   236  	}
   237  
   238  	block, _ := dpos.GetBestBlock()
   239  	if block == nil {
   240  		return nil
   241  	}
   242  
   243  	if !isBpTiming(block, s) {
   244  		return nil
   245  	}
   246  
   247  	return &bpInfo{
   248  		ChainDB:   dpos.ChainDB,
   249  		bestBlock: block,
   250  		slot:      s,
   251  	}
   252  }
   253  
   254  // ConsensusInfo returns the basic DPoS-related info.
   255  func (dpos *DPoS) ConsensusInfo() *types.ConsensusInfo {
   256  	withLock := func(fn func()) {
   257  		dpos.RLock()
   258  		defer dpos.RUnlock()
   259  		fn()
   260  	}
   261  
   262  	ci := &types.ConsensusInfo{Type: GetName()}
   263  	withLock(func() {
   264  		ci.Bps = dpos.bpc.BPs()
   265  
   266  	})
   267  
   268  	if dpos.done {
   269  		var lpbNo types.BlockNo
   270  
   271  		withLock(func() {
   272  			lpbNo = dpos.lpbNo()
   273  		})
   274  
   275  		if lpbNo > 0 {
   276  			if block, err := dpos.GetBlockByNo(lpbNo); err == nil {
   277  				type lpbInfo struct {
   278  					BPID      string
   279  					Height    types.BlockNo
   280  					Hash      string
   281  					Timestamp string
   282  				}
   283  				s := struct {
   284  					NodeID              string
   285  					RecentBlockProduced lpbInfo
   286  				}{
   287  					NodeID: dpos.bf.ID,
   288  					RecentBlockProduced: lpbInfo{
   289  						BPID:      block.BPID2Str(),
   290  						Height:    lpbNo,
   291  						Hash:      block.ID(),
   292  						Timestamp: block.Localtime().String(),
   293  					},
   294  				}
   295  				if m, err := json.Marshal(s); err == nil {
   296  					ci.Info = string(m)
   297  				}
   298  			}
   299  		}
   300  	}
   301  
   302  	return ci
   303  }
   304  
   305  var dummyRaft consensus.DummyRaftAccessor
   306  
   307  func (dpos *DPoS) RaftAccessor() consensus.AergoRaftAccessor {
   308  	return &dummyRaft
   309  }
   310  
   311  func isBpTiming(block *types.Block, s *slot.Slot) bool {
   312  	blockSlot := slot.NewFromUnixNano(block.Header.Timestamp)
   313  	// The block corresponding to the current slot has already been generated.
   314  	if slot.LessEqual(s, blockSlot) {
   315  		return false
   316  	}
   317  
   318  	// Check whether the remaining time is enough until the next block
   319  	// generation time.
   320  	if !slot.IsNextTo(s, blockSlot) && !s.TimesUp() {
   321  		return false
   322  	}
   323  
   324  	timeLeft := s.RemainingTimeMS()
   325  	if timeLeft < 0 {
   326  		logger.Debug().Int64("remaining time", timeLeft).Msg("no time left to produce block")
   327  		return false
   328  	}
   329  
   330  	return true
   331  }
   332  
   333  func (dpos *DPoS) NeedNotify() bool {
   334  	return true
   335  }
   336  
   337  func (dpos *DPoS) HasWAL() bool {
   338  	return false
   339  }
   340  
   341  func (dpos *DPoS) IsForkEnable() bool {
   342  	return true
   343  }
   344  
   345  func (dpos *DPoS) IsConnectedBlock(block *types.Block) bool {
   346  	_, err := dpos.ChainDB.GetBlock(block.BlockHash())
   347  	if err == nil {
   348  		return true
   349  	}
   350  
   351  	return false
   352  }
   353  
   354  func (dpos *DPoS) ConfChange(req *types.MembershipChange) (*consensus.Member, error) {
   355  	return nil, consensus.ErrNotSupportedMethod
   356  }
   357  
   358  func (dpos *DPoS) ConfChangeInfo(requestID uint64) (*types.ConfChangeProgress, error) {
   359  	return nil, consensus.ErrNotSupportedMethod
   360  }
   361  
   362  func (dpos *DPoS) MakeConfChangeProposal(req *types.MembershipChange) (*consensus.ConfChangePropose, error) {
   363  	return nil, consensus.ErrNotSupportedMethod
   364  }
   365  
   366  func (dpos *DPoS) ClusterInfo(bestBlockHash []byte) *types.GetClusterInfoResponse {
   367  	return &types.GetClusterInfoResponse{ChainID: nil, Error: consensus.ErrNotSupportedMethod.Error(), MbrAttrs: nil, HardStateInfo: nil}
   368  }
   369  
   370  func ValidateGenesis(genesis *types.Genesis) error {
   371  	return nil
   372  }