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 }