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 }