github.com/iotexproject/iotex-core@v1.14.1-rc1/consensus/scheme/rolldpos/endorsementmanager.go (about)

     1  // Copyright (c) 2019 IoTeX Foundation
     2  // This source code is provided 'as is' and no warranties are given as to title or non-infringement, merchantability
     3  // or fitness for purpose and, to the extent permitted by law, all liability for your use of the code is disclaimed.
     4  // This source code is governed by Apache License 2.0 that can be found in the LICENSE file.
     5  
     6  package rolldpos
     7  
     8  import (
     9  	"encoding/hex"
    10  	"time"
    11  
    12  	"github.com/pkg/errors"
    13  	"go.uber.org/zap"
    14  	"google.golang.org/protobuf/proto"
    15  
    16  	"github.com/iotexproject/iotex-core/blockchain/block"
    17  	"github.com/iotexproject/iotex-core/consensus/scheme/rolldpos/endorsementpb"
    18  	"github.com/iotexproject/iotex-core/db"
    19  	"github.com/iotexproject/iotex-core/endorsement"
    20  	"github.com/iotexproject/iotex-core/pkg/log"
    21  )
    22  
    23  const (
    24  	_eManagerNS = "edm"
    25  )
    26  
    27  var (
    28  	// ErrExpiredEndorsement indicates that the endorsement is expired
    29  	ErrExpiredEndorsement = errors.New("the endorsement has been replaced or expired")
    30  	_statusKey            = []byte("status")
    31  )
    32  
    33  // EndorsedByMajorityFunc defines a function to give an information of consensus status
    34  type EndorsedByMajorityFunc func(blockHash []byte, topics []ConsensusVoteTopic) bool
    35  
    36  type endorserEndorsementCollection struct {
    37  	endorsements map[ConsensusVoteTopic]*endorsement.Endorsement
    38  }
    39  
    40  func newEndorserEndorsementCollection() *endorserEndorsementCollection {
    41  	return &endorserEndorsementCollection{
    42  		endorsements: map[ConsensusVoteTopic]*endorsement.Endorsement{},
    43  	}
    44  }
    45  
    46  func (ee *endorserEndorsementCollection) fromProto(endorserPro *endorsementpb.EndorserEndorsementCollection) error {
    47  	ee.endorsements = make(map[ConsensusVoteTopic]*endorsement.Endorsement)
    48  	for index := range endorserPro.Topics {
    49  		endorse := &endorsement.Endorsement{}
    50  		if err := endorse.LoadProto(endorserPro.Endorsements[index]); err != nil {
    51  			return err
    52  		}
    53  		ee.endorsements[ConsensusVoteTopic(endorserPro.Topics[index])] = endorse
    54  	}
    55  	return nil
    56  }
    57  
    58  func (ee *endorserEndorsementCollection) toProto(endorser string) (*endorsementpb.EndorserEndorsementCollection, error) {
    59  	eeProto := &endorsementpb.EndorserEndorsementCollection{}
    60  	eeProto.Endorser = endorser
    61  	for topic, endorse := range ee.endorsements {
    62  		eeProto.Topics = append(eeProto.Topics, uint32(topic))
    63  		ioEndorsement, err := endorse.Proto()
    64  		if err != nil {
    65  			return nil, err
    66  		}
    67  		eeProto.Endorsements = append(eeProto.Endorsements, ioEndorsement)
    68  	}
    69  	return eeProto, nil
    70  }
    71  
    72  func (ee *endorserEndorsementCollection) AddEndorsement(
    73  	topic ConsensusVoteTopic,
    74  	en *endorsement.Endorsement,
    75  ) error {
    76  	if e, exists := ee.endorsements[topic]; exists {
    77  		if e.Timestamp().After(en.Timestamp()) {
    78  			return ErrExpiredEndorsement
    79  		}
    80  	}
    81  	ee.endorsements[topic] = en
    82  
    83  	return nil
    84  }
    85  
    86  func (ee *endorserEndorsementCollection) Cleanup(timestamp time.Time) *endorserEndorsementCollection {
    87  	cleaned := newEndorserEndorsementCollection()
    88  	if e, exists := ee.endorsements[PROPOSAL]; exists {
    89  		if !e.Timestamp().Before(timestamp) {
    90  			cleaned.endorsements[PROPOSAL] = e
    91  		}
    92  	}
    93  	if e, exists := ee.endorsements[LOCK]; exists {
    94  		if !e.Timestamp().Before(timestamp) {
    95  			cleaned.endorsements[LOCK] = e
    96  		}
    97  	}
    98  	if e, exists := ee.endorsements[COMMIT]; exists {
    99  		cleaned.endorsements[COMMIT] = e
   100  	}
   101  	return cleaned
   102  }
   103  
   104  func (ee *endorserEndorsementCollection) Endorsement(
   105  	topic ConsensusVoteTopic,
   106  ) *endorsement.Endorsement {
   107  	return ee.endorsements[topic]
   108  }
   109  
   110  type blockEndorsementCollection struct {
   111  	blk       *block.Block
   112  	endorsers map[string]*endorserEndorsementCollection
   113  }
   114  
   115  func newBlockEndorsementCollection(blk *block.Block) *blockEndorsementCollection {
   116  	return &blockEndorsementCollection{
   117  		blk:       blk,
   118  		endorsers: map[string]*endorserEndorsementCollection{},
   119  	}
   120  }
   121  
   122  func (bc *blockEndorsementCollection) fromProto(blockPro *endorsementpb.BlockEndorsementCollection, deserializer *block.Deserializer) error {
   123  	bc.endorsers = make(map[string]*endorserEndorsementCollection)
   124  	if blockPro.Blk == nil {
   125  		bc.blk = nil
   126  	} else {
   127  		blk, err := deserializer.FromBlockProto(blockPro.Blk)
   128  		if err != nil {
   129  			return err
   130  		}
   131  		bc.blk = blk
   132  	}
   133  	for _, endorsement := range blockPro.BlockMap {
   134  		ee := &endorserEndorsementCollection{}
   135  		if err := ee.fromProto(endorsement); err != nil {
   136  			return err
   137  		}
   138  		bc.endorsers[endorsement.Endorser] = ee
   139  	}
   140  	return nil
   141  }
   142  
   143  func (bc *blockEndorsementCollection) toProto() (*endorsementpb.BlockEndorsementCollection, error) {
   144  	bcProto := &endorsementpb.BlockEndorsementCollection{}
   145  	if bc.blk != nil {
   146  		bcProto.Blk = bc.blk.ConvertToBlockPb()
   147  	}
   148  
   149  	for s, endorse := range bc.endorsers {
   150  		ioEndorsement, err := endorse.toProto(s)
   151  		if err != nil {
   152  			return nil, err
   153  		}
   154  		bcProto.BlockMap = append(bcProto.BlockMap, ioEndorsement)
   155  	}
   156  	return bcProto, nil
   157  }
   158  
   159  func (bc *blockEndorsementCollection) SetBlock(blk *block.Block) error {
   160  	bc.blk = blk
   161  	return nil
   162  }
   163  
   164  func (bc *blockEndorsementCollection) Block() *block.Block {
   165  	return bc.blk
   166  }
   167  
   168  func (bc *blockEndorsementCollection) AddEndorsement(
   169  	topic ConsensusVoteTopic,
   170  	en *endorsement.Endorsement,
   171  ) error {
   172  	endorser := en.Endorser().HexString()
   173  	ee, exists := bc.endorsers[endorser]
   174  	if !exists {
   175  		ee = newEndorserEndorsementCollection()
   176  	}
   177  	if err := ee.AddEndorsement(topic, en); err != nil {
   178  		return err
   179  	}
   180  	bc.endorsers[endorser] = ee
   181  
   182  	return nil
   183  }
   184  
   185  func (bc *blockEndorsementCollection) Endorsement(
   186  	endorser string,
   187  	topic ConsensusVoteTopic,
   188  ) *endorsement.Endorsement {
   189  	ee, exists := bc.endorsers[endorser]
   190  	if !exists {
   191  		return nil
   192  	}
   193  	return ee.Endorsement(topic)
   194  }
   195  
   196  func (bc *blockEndorsementCollection) Cleanup(timestamp time.Time) *blockEndorsementCollection {
   197  	cleaned := newBlockEndorsementCollection(bc.blk)
   198  	for endorser, ee := range bc.endorsers {
   199  		cleaned.endorsers[endorser] = ee.Cleanup(timestamp)
   200  	}
   201  	return cleaned
   202  }
   203  
   204  func (bc *blockEndorsementCollection) Endorsements(
   205  	topics []ConsensusVoteTopic,
   206  ) []*endorsement.Endorsement {
   207  	endorsements := []*endorsement.Endorsement{}
   208  	for _, ee := range bc.endorsers {
   209  		for _, topic := range topics {
   210  			if en := ee.Endorsement(topic); en != nil {
   211  				endorsements = append(endorsements, en)
   212  				break
   213  			}
   214  		}
   215  	}
   216  	return endorsements
   217  }
   218  
   219  type endorsementManager struct {
   220  	isMajorityFunc  EndorsedByMajorityFunc
   221  	eManagerDB      db.KVStore
   222  	collections     map[string]*blockEndorsementCollection
   223  	cachedMintedBlk *block.Block
   224  }
   225  
   226  func newEndorsementManager(eManagerDB db.KVStore, deserializer *block.Deserializer) (*endorsementManager, error) {
   227  	if eManagerDB == nil {
   228  		return &endorsementManager{
   229  			eManagerDB:      nil,
   230  			collections:     map[string]*blockEndorsementCollection{},
   231  			cachedMintedBlk: nil,
   232  		}, nil
   233  	}
   234  	bytes, err := eManagerDB.Get(_eManagerNS, _statusKey)
   235  	switch errors.Cause(err) {
   236  	case nil:
   237  		// Get from DB
   238  		manager := &endorsementManager{eManagerDB: eManagerDB}
   239  		managerProto := &endorsementpb.EndorsementManager{}
   240  		if err = proto.Unmarshal(bytes, managerProto); err != nil {
   241  			return nil, err
   242  		}
   243  		if err = manager.fromProto(managerProto, deserializer); err != nil {
   244  			return nil, err
   245  		}
   246  		manager.eManagerDB = eManagerDB
   247  		return manager, nil
   248  	case db.ErrNotExist:
   249  		// If DB doesn't have any information
   250  		log.L().Info("First initializing DB")
   251  		return &endorsementManager{
   252  			eManagerDB:      eManagerDB,
   253  			collections:     map[string]*blockEndorsementCollection{},
   254  			cachedMintedBlk: nil,
   255  		}, nil
   256  	default:
   257  		return nil, err
   258  	}
   259  }
   260  
   261  func (m *endorsementManager) PutEndorsementManagerToDB() error {
   262  	managerProto, err := m.toProto()
   263  	if err != nil {
   264  		return err
   265  	}
   266  	valBytes, err := proto.Marshal(managerProto)
   267  	if err != nil {
   268  		return err
   269  	}
   270  	err = m.eManagerDB.Put(_eManagerNS, _statusKey, valBytes)
   271  	if err != nil {
   272  		return err
   273  	}
   274  	return nil
   275  }
   276  
   277  func (m *endorsementManager) SetIsMarjorityFunc(isMajorityFunc EndorsedByMajorityFunc) {
   278  	m.isMajorityFunc = isMajorityFunc
   279  }
   280  
   281  func (m *endorsementManager) fromProto(managerPro *endorsementpb.EndorsementManager, deserializer *block.Deserializer) error {
   282  	m.collections = make(map[string]*blockEndorsementCollection)
   283  	for i, block := range managerPro.BlockEndorsements {
   284  		bc := &blockEndorsementCollection{}
   285  		if err := bc.fromProto(block, deserializer); err != nil {
   286  			return err
   287  		}
   288  		m.collections[managerPro.BlkHash[i]] = bc
   289  	}
   290  	if managerPro.CachedMintedBlk != nil {
   291  		blk, err := deserializer.FromBlockProto(managerPro.CachedMintedBlk)
   292  		if err != nil {
   293  			return err
   294  		}
   295  		m.cachedMintedBlk = blk
   296  	}
   297  	return nil
   298  }
   299  
   300  func (m *endorsementManager) toProto() (*endorsementpb.EndorsementManager, error) {
   301  	mc := &endorsementpb.EndorsementManager{}
   302  	for encodedBlockHash, block := range m.collections {
   303  		ioBlockEndorsement, err := block.toProto()
   304  		if err != nil {
   305  			return nil, err
   306  		}
   307  		mc.BlkHash = append(mc.BlkHash, encodedBlockHash)
   308  		mc.BlockEndorsements = append(mc.BlockEndorsements, ioBlockEndorsement)
   309  	}
   310  	if m.cachedMintedBlk != nil {
   311  		mc.CachedMintedBlk = m.cachedMintedBlk.ConvertToBlockPb()
   312  	}
   313  	return mc, nil
   314  }
   315  
   316  func (m *endorsementManager) CollectionByBlockHash(blkHash []byte) *blockEndorsementCollection {
   317  	encodedBlockHash := encodeToString(blkHash)
   318  	collections, exists := m.collections[encodedBlockHash]
   319  	if !exists {
   320  		return nil
   321  	}
   322  	return collections
   323  }
   324  
   325  func (m *endorsementManager) Size() int {
   326  	return len(m.collections)
   327  }
   328  
   329  func (m *endorsementManager) SizeWithBlock() int {
   330  	size := 0
   331  	for _, c := range m.collections {
   332  		if c.Block() != nil {
   333  			size++
   334  		}
   335  	}
   336  	return size
   337  }
   338  
   339  func (m *endorsementManager) RegisterBlock(blk *block.Block) error {
   340  	blkHash := blk.HashBlock()
   341  	encodedBlockHash := encodeToString(blkHash[:])
   342  	if c, exists := m.collections[encodedBlockHash]; exists {
   343  		return c.SetBlock(blk)
   344  	}
   345  	m.collections[encodedBlockHash] = newBlockEndorsementCollection(blk)
   346  
   347  	if m.eManagerDB != nil {
   348  		return m.PutEndorsementManagerToDB()
   349  	}
   350  	return nil
   351  }
   352  
   353  func (m *endorsementManager) AddVoteEndorsement(
   354  	vote *ConsensusVote,
   355  	en *endorsement.Endorsement,
   356  ) error {
   357  	var beforeVote, afterVote bool
   358  	if m.isMajorityFunc != nil {
   359  		beforeVote = m.isMajorityFunc(vote.BlockHash(), []ConsensusVoteTopic{vote.Topic()})
   360  	}
   361  	encoded := encodeToString(vote.BlockHash())
   362  	c, exists := m.collections[encoded]
   363  	if !exists {
   364  		c = newBlockEndorsementCollection(nil)
   365  	}
   366  	if err := c.AddEndorsement(vote.Topic(), en); err != nil {
   367  		return err
   368  	}
   369  	m.collections[encoded] = c
   370  
   371  	if m.eManagerDB != nil && m.isMajorityFunc != nil {
   372  		afterVote = m.isMajorityFunc(vote.BlockHash(), []ConsensusVoteTopic{vote.Topic()})
   373  		if !beforeVote && afterVote {
   374  			//put into DB only it changes the status of consensus
   375  			return m.PutEndorsementManagerToDB()
   376  		}
   377  	}
   378  	return nil
   379  }
   380  
   381  func (m *endorsementManager) SetMintedBlock(blk *block.Block) error {
   382  	m.cachedMintedBlk = blk
   383  	if m.eManagerDB != nil {
   384  		return m.PutEndorsementManagerToDB()
   385  	}
   386  	return nil
   387  }
   388  
   389  func (m *endorsementManager) CachedMintedBlock() *block.Block {
   390  	return m.cachedMintedBlk
   391  }
   392  
   393  func (m *endorsementManager) Cleanup(timestamp time.Time) error {
   394  	if !timestamp.IsZero() {
   395  		for encoded, c := range m.collections {
   396  			m.collections[encoded] = c.Cleanup(timestamp)
   397  		}
   398  	} else {
   399  		m.collections = map[string]*blockEndorsementCollection{}
   400  	}
   401  	if m.cachedMintedBlk != nil {
   402  		if timestamp.IsZero() || m.cachedMintedBlk.Timestamp().Before(timestamp) {
   403  			// in case that the cached minted block is outdated, clean up
   404  			m.cachedMintedBlk = nil
   405  		}
   406  	}
   407  	if m.eManagerDB != nil {
   408  		return m.PutEndorsementManagerToDB()
   409  	}
   410  	return nil
   411  }
   412  
   413  func (m *endorsementManager) Log(
   414  	logger *zap.Logger,
   415  	delegates []string,
   416  ) *zap.Logger {
   417  	for encoded, c := range m.collections {
   418  		proposalEndorsements := c.Endorsements(
   419  			[]ConsensusVoteTopic{PROPOSAL},
   420  		)
   421  		lockEndorsements := c.Endorsements(
   422  			[]ConsensusVoteTopic{LOCK},
   423  		)
   424  		commitEndorsments := c.Endorsements(
   425  			[]ConsensusVoteTopic{COMMIT},
   426  		)
   427  		return logger.With(
   428  			zap.Int("numProposals:"+encoded, len(proposalEndorsements)),
   429  			zap.Int("numLocks:"+encoded, len(lockEndorsements)),
   430  			zap.Int("numCommits:"+encoded, len(commitEndorsments)),
   431  		)
   432  	}
   433  	return logger
   434  }
   435  
   436  func encodeToString(hash []byte) string {
   437  	return hex.EncodeToString(hash)
   438  }