github.com/annchain/OG@v0.0.9/poc/tendermint/client.go (about) 1 package tendermint 2 3 import ( 4 "fmt" 5 "github.com/sirupsen/logrus" 6 "strings" 7 "time" 8 ) 9 10 // Partner is a participant in the consensus. 11 type Partner interface { 12 EventLoop() 13 StartNewEra(height int, round int) 14 SetPeers(peers []Partner) 15 GetIncomingMessageChannel() chan Message 16 GetOutgoingMessageChannel() chan Message 17 GetWaiterTimeoutChannel() chan *WaiterRequest 18 GetId() int 19 } 20 21 type PartnerBase struct { 22 Id int 23 IncomingMessageChannel chan Message 24 OutgoingMessageChannel chan Message 25 WaiterTimeoutChannel chan *WaiterRequest 26 } 27 28 // HeightRound is the current progress of the consensus. 29 // Height is the block height, round is the sub-progress if no consensus can be easily reached 30 type HeightRound struct { 31 Height int 32 Round int 33 } 34 35 func (h *HeightRound) String() string { 36 return fmt.Sprintf("[%d-%d]", h.Height, h.Round) 37 } 38 39 // IsAfter judges whether self is a higher HeightRound 40 func (h *HeightRound) IsAfter(o HeightRound) bool { 41 return h.Height > o.Height || 42 (h.Height == o.Height && h.Round > o.Round) 43 } 44 45 // IsAfterOrEqual judges whether self is a higher or equal HeightRound 46 func (h *HeightRound) IsAfterOrEqual(o HeightRound) bool { 47 return h.Height > o.Height || 48 (h.Height == o.Height && h.Round >= o.Round) 49 } 50 51 // IsAfterOrEqual judges whether self is a lower HeightRound 52 func (h *HeightRound) IsBefore(o HeightRound) bool { 53 return h.Height < o.Height || 54 (h.Height == o.Height && h.Round < o.Round) 55 } 56 57 // HeightRoundState is the structure for each Height/Round 58 // Always keep this state that is higher than current in Partner.States map in order not to miss future things 59 type HeightRoundState struct { 60 MessageProposal *MessageProposal // the proposal received in this round 61 LockedValue Proposal 62 LockedRound int 63 ValidValue Proposal 64 ValidRound int 65 Decision interface{} // final decision of mine in this round 66 PreVotes []*MessageCommonVote // other peers' PreVotes 67 PreCommits []*MessageCommonVote // other peers' PreCommits 68 Sources map[int]bool // for line 55, who send future round so that I may advance? 69 StepTypeEqualPreVoteTriggered bool // for line 34, FIRST time trigger 70 StepTypeEqualOrLargerPreVoteTriggered bool // for line 36, FIRST time trigger 71 StepTypeEqualPreCommitTriggered bool // for line 47, FIRST time trigger 72 Step StepType // current step in this round 73 } 74 75 func NewHeightRoundState(total int) *HeightRoundState { 76 return &HeightRoundState{ 77 LockedRound: -1, 78 ValidRound: -1, 79 PreVotes: make([]*MessageCommonVote, total), 80 PreCommits: make([]*MessageCommonVote, total), 81 Sources: make(map[int]bool), 82 } 83 } 84 85 // DefaultPartner implements a Tendermint client according to "The latest gossip on BFT consensus" 86 // Destroy and use a new one upon peers changing. 87 type DefaultPartner struct { 88 PartnerBase 89 CurrentHR HeightRound // Partner's current Height/Round 90 blockTime time.Duration // interval between proposal being generated 91 N int // total number of participants 92 F int // max number of Byzantines 93 Peers []Partner // All peers 94 quit chan bool 95 waiter *Waiter // waiter for some state changes 96 States map[HeightRound]*HeightRoundState // for line 55, record state for every HeightRound 97 } 98 99 func (p *DefaultPartner) GetWaiterTimeoutChannel() chan *WaiterRequest { 100 return p.WaiterTimeoutChannel 101 } 102 103 func (p *DefaultPartner) GetIncomingMessageChannel() chan Message { 104 return p.IncomingMessageChannel 105 } 106 107 func (p *DefaultPartner) GetOutgoingMessageChannel() chan Message { 108 return p.OutgoingMessageChannel 109 } 110 111 func (p *DefaultPartner) GetId() int { 112 return p.Id 113 } 114 115 func (p *DefaultPartner) SetPeers(peers []Partner) { 116 p.Peers = peers 117 } 118 119 func NewPartner(nbParticipants int, id int, blockTime time.Duration) *DefaultPartner { 120 p := &DefaultPartner{ 121 N: nbParticipants, 122 F: (nbParticipants - 1) / 3, 123 blockTime: blockTime, 124 PartnerBase: PartnerBase{ 125 Id: id, 126 IncomingMessageChannel: make(chan Message, 10), 127 OutgoingMessageChannel: make(chan Message, 10), 128 WaiterTimeoutChannel: make(chan *WaiterRequest, 10), 129 }, 130 quit: make(chan bool), 131 States: make(map[HeightRound]*HeightRoundState), 132 } 133 p.waiter = NewWaiter(p.GetWaiterTimeoutChannel()) 134 go p.waiter.StartEventLoop() 135 return p 136 } 137 138 // StartNewEra is called once height or round needs to be changed. 139 func (p *DefaultPartner) StartNewEra(height int, round int) { 140 hr := p.CurrentHR 141 if height-hr.Height > 1 { 142 logrus.WithField("height", height).Warn("height is much higher than current. Indicating packet loss or severe behind.") 143 } 144 hr.Height = height 145 hr.Round = round 146 147 logrus.WithFields(logrus.Fields{ 148 "IM": p.Id, 149 "currentHR": p.CurrentHR.String(), 150 "newHR": hr.String(), 151 }).Debug("Starting new round") 152 153 currState, _ := p.initHeightRound(hr) 154 // update partner height 155 p.CurrentHR = hr 156 157 p.WipeOldStates() 158 p.changeStep(StepTypePropose) 159 160 if p.Id == p.Proposer(p.CurrentHR) { 161 logrus.WithField("IM", p.Id).WithField("hr", p.CurrentHR.String()).Info("I'm the proposer") 162 var proposal Proposal 163 if currState.ValidValue != nil { 164 proposal = currState.ValidValue 165 } else { 166 proposal = p.GetValue() 167 } 168 // broadcast 169 p.Broadcast(MessageTypeProposal, p.CurrentHR, proposal, currState.ValidRound) 170 } else { 171 p.WaitStepTimeout(StepTypePropose, TimeoutPropose, p.CurrentHR, p.OnTimeoutPropose) 172 } 173 } 174 175 func (p *DefaultPartner) EventLoop() { 176 go p.send() 177 go p.receive() 178 } 179 180 // send is just for outgoing messages. It should not change any state of local tendermint 181 func (p *DefaultPartner) send() { 182 timer := time.NewTimer(time.Second * 7) 183 for { 184 timer.Reset(time.Second * 7) 185 select { 186 case <-p.quit: 187 break 188 case <-timer.C: 189 logrus.WithField("IM", p.Id).Warn("Blocked reading outgoing") 190 p.dumpAll("blocked reading outgoing") 191 case msg := <-p.OutgoingMessageChannel: 192 for _, peer := range p.Peers { 193 logrus.WithFields(logrus.Fields{ 194 "IM": p.Id, 195 "hr": p.CurrentHR.String(), 196 "from": p.Id, 197 "to": peer.GetId(), 198 "msg": msg.String(), 199 }).Debug("Out") 200 go func(targetPeer Partner) { 201 //time.Sleep(time.Duration(300 + rand.Intn(100)) * time.Millisecond) 202 //ffchan.NewTimeoutSenderShort(targetPeer.GetIncomingMessageChannel(), msg, "broadcasting") 203 targetPeer.GetIncomingMessageChannel() <- msg 204 }(peer) 205 } 206 } 207 } 208 } 209 210 // receive prevents concurrent state updates by allowing only one channel to be read per loop 211 // Any action which involves state updates should be in this select clause 212 func (p *DefaultPartner) receive() { 213 timer := time.NewTimer(time.Second * 7) 214 for { 215 timer.Reset(time.Second * 7) 216 select { 217 case <-p.quit: 218 break 219 case v := <-p.WaiterTimeoutChannel: 220 context := v.Context.(*TendermintContext) 221 logrus.WithFields(logrus.Fields{ 222 "step": context.StepType.String(), 223 "IM": p.Id, 224 "hr": context.HeightRound.String(), 225 }).Warn("wait step timeout") 226 p.dumpAll("wait step timeout") 227 v.TimeoutCallback(v.Context) 228 case <-timer.C: 229 logrus.WithField("IM", p.Id).Warn("Blocked reading incoming") 230 //p.dumpAll("blocked reading incoming") 231 case msg := <-p.IncomingMessageChannel: 232 p.handleMessage(msg) 233 } 234 } 235 } 236 237 // Proposer returns current round proposer. Now simply round robin 238 func (p *DefaultPartner) Proposer(hr HeightRound) int { 239 //return 3 240 return (hr.Height + hr.Round) % p.N 241 } 242 243 // GetValue generates the value requiring consensus 244 func (p *DefaultPartner) GetValue() Proposal { 245 logrus.WithField("blocktime", p.blockTime).Info("will return a proposal after some time") 246 time.Sleep(p.blockTime) 247 logrus.Info("proposal generated") 248 v := fmt.Sprintf("■■■%d %d■■■", p.CurrentHR.Height, p.CurrentHR.Round) 249 return StringProposal(v) 250 } 251 252 // Multicast announce messages to all partners 253 func (p *DefaultPartner) Broadcast(messageType MessageType, hr HeightRound, content Proposal, validRound int) { 254 m := Message{ 255 Type: messageType, 256 } 257 basicMessage := BasicMessage{ 258 HeightRound: hr, 259 SourceId: p.Id, 260 } 261 idv := "" 262 if content != nil { 263 idv = content.GetId() 264 } 265 switch messageType { 266 case MessageTypeProposal: 267 m.Payload = MessageProposal{ 268 BasicMessage: basicMessage, 269 Value: content, 270 ValidRound: validRound, 271 } 272 case MessageTypePreVote: 273 m.Payload = MessageCommonVote{ 274 BasicMessage: basicMessage, 275 Idv: idv, 276 } 277 case MessageTypePreCommit: 278 m.Payload = MessageCommonVote{ 279 BasicMessage: basicMessage, 280 Idv: idv, 281 } 282 } 283 p.OutgoingMessageChannel <- m 284 //ffchan.NewTimeoutSenderShort(p.OutgoingMessageChannel, m, "") 285 } 286 287 // OnTimeoutPropose is the callback after staying too long on propose step 288 func (p *DefaultPartner) OnTimeoutPropose(context WaiterContext) { 289 v := context.(*TendermintContext) 290 if v.HeightRound == p.CurrentHR && p.States[p.CurrentHR].Step == StepTypePropose { 291 p.Broadcast(MessageTypePreVote, p.CurrentHR, nil, 0) 292 p.changeStep(StepTypePreVote) 293 } 294 } 295 296 // OnTimeoutPreVote is the callback after staying too long on prevote step 297 func (p *DefaultPartner) OnTimeoutPreVote(context WaiterContext) { 298 v := context.(*TendermintContext) 299 if v.HeightRound == p.CurrentHR && p.States[p.CurrentHR].Step == StepTypePreVote { 300 p.Broadcast(MessageTypePreCommit, p.CurrentHR, nil, 0) 301 p.changeStep(StepTypePreCommit) 302 } 303 } 304 305 // OnTimeoutPreCommit is the callback after staying too long on precommit step 306 func (p *DefaultPartner) OnTimeoutPreCommit(context WaiterContext) { 307 v := context.(*TendermintContext) 308 if v.HeightRound == p.CurrentHR { 309 p.StartNewEra(v.HeightRound.Height, v.HeightRound.Round+1) 310 } else { 311 logrus.Warn(v.HeightRound, " diffff ", p.CurrentHR) 312 } 313 } 314 315 // WaitStepTimeout waits for a centain time if stepType stays too long 316 func (p *DefaultPartner) WaitStepTimeout(stepType StepType, timeout time.Duration, hr HeightRound, timeoutCallback func(WaiterContext)) { 317 p.waiter.UpdateRequest(&WaiterRequest{ 318 WaitTime: timeout, 319 TimeoutCallback: timeoutCallback, 320 Context: &TendermintContext{ 321 HeightRound: hr, 322 StepType: stepType, 323 }, 324 }) 325 } 326 327 func (p *DefaultPartner) handleMessage(message Message) { 328 switch message.Type { 329 case MessageTypeProposal: 330 msg := message.Payload.(MessageProposal) 331 if needHandle := p.checkRound(&msg.BasicMessage); !needHandle { 332 // out-of-date messages, ignore 333 break 334 } 335 logrus.WithFields(logrus.Fields{ 336 "IM": p.Id, 337 "hr": p.CurrentHR.String(), 338 "type": message.Type.String(), 339 "from": msg.SourceId, 340 "fromHr": msg.HeightRound.String(), 341 "value": msg.Value, 342 }).Debug("In") 343 p.handleProposal(&msg) 344 case MessageTypePreVote: 345 msg := message.Payload.(MessageCommonVote) 346 if needHandle := p.checkRound(&msg.BasicMessage); !needHandle { 347 // out-of-date messages, ignore 348 logrus.Debug("no needHandle") 349 break 350 } 351 p.States[msg.HeightRound].PreVotes[msg.SourceId] = &msg 352 logrus.WithFields(logrus.Fields{ 353 "IM": p.Id, 354 "hr": p.CurrentHR.String(), 355 "type": message.Type.String(), 356 "from": msg.SourceId, 357 "fromHr": msg.HeightRound.String(), 358 }).Debug("In") 359 p.handlePreVote(&msg) 360 case MessageTypePreCommit: 361 msg := message.Payload.(MessageCommonVote) 362 if needHandle := p.checkRound(&msg.BasicMessage); !needHandle { 363 // out-of-date messages, ignore 364 logrus.Debug("no needHandle") 365 break 366 } 367 p.States[msg.HeightRound].PreCommits[msg.SourceId] = &msg 368 logrus.WithFields(logrus.Fields{ 369 "IM": p.Id, 370 "hr": p.CurrentHR.String(), 371 "type": message.Type.String(), 372 "from": msg.SourceId, 373 "fromHr": msg.HeightRound.String(), 374 }).Debug("In") 375 p.handlePreCommit(&msg) 376 } 377 } 378 func (p *DefaultPartner) handleProposal(proposal *MessageProposal) { 379 state, ok := p.States[proposal.HeightRound] 380 if !ok { 381 panic("must exists") 382 } 383 state.MessageProposal = proposal 384 // rule line 22 385 if state.Step == StepTypePropose { 386 if p.valid(proposal.Value) && (state.LockedRound == -1 || state.LockedValue.Equal(proposal.Value)) { 387 p.Broadcast(MessageTypePreVote, proposal.HeightRound, proposal.Value, 0) 388 } else { 389 p.Broadcast(MessageTypePreVote, proposal.HeightRound, nil, 0) 390 } 391 p.changeStep(StepTypePreVote) 392 } 393 394 // rule line 28 395 count := p.count(MessageTypePreVote, proposal.HeightRound.Height, proposal.ValidRound, MatchTypeByValue, proposal.Value.GetId()) 396 if count >= 2*p.F+1 { 397 if state.Step == StepTypePropose && (proposal.ValidRound >= 0 && proposal.ValidRound < p.CurrentHR.Round) { 398 if p.valid(proposal.Value) && (state.LockedRound <= proposal.ValidRound || state.LockedValue.Equal(proposal.Value)) { 399 p.Broadcast(MessageTypePreVote, proposal.HeightRound, proposal.Value, 0) 400 } else { 401 p.Broadcast(MessageTypePreVote, proposal.HeightRound, nil, 0) 402 } 403 p.changeStep(StepTypePreVote) 404 } 405 } 406 } 407 func (p *DefaultPartner) handlePreVote(vote *MessageCommonVote) { 408 // rule line 34 409 count := p.count(MessageTypePreVote, vote.HeightRound.Height, vote.HeightRound.Round, MatchTypeAny, "") 410 state, ok := p.States[vote.HeightRound] 411 if !ok { 412 panic("should exists: " + vote.HeightRound.String()) 413 } 414 if count >= 2*p.F+1 { 415 if state.Step == StepTypePreVote && !state.StepTypeEqualPreVoteTriggered { 416 logrus.WithField("IM", p.Id).WithField("hr", vote.HeightRound.String()).Debug("prevote counter is more than 2f+1 #1") 417 state.StepTypeEqualPreVoteTriggered = true 418 p.WaitStepTimeout(StepTypePreVote, TimeoutPreVote, vote.HeightRound, p.OnTimeoutPreVote) 419 } 420 } 421 // rule line 36 422 if state.MessageProposal != nil && count >= 2*p.F+1 { 423 if p.valid(state.MessageProposal.Value) && state.Step >= StepTypePreVote && !state.StepTypeEqualOrLargerPreVoteTriggered { 424 logrus.WithField("IM", p.Id).WithField("hr", vote.HeightRound.String()).Debug("prevote counter is more than 2f+1 #2") 425 state.StepTypeEqualOrLargerPreVoteTriggered = true 426 if state.Step == StepTypePreVote { 427 state.LockedValue = state.MessageProposal.Value 428 state.LockedRound = p.CurrentHR.Round 429 p.Broadcast(MessageTypePreCommit, vote.HeightRound, state.MessageProposal.Value, 0) 430 p.changeStep(StepTypePreCommit) 431 } 432 state.ValidValue = state.MessageProposal.Value 433 state.ValidRound = p.CurrentHR.Round 434 } 435 } 436 // rule line 44 437 count = p.count(MessageTypePreVote, vote.HeightRound.Height, vote.HeightRound.Round, MatchTypeNil, "") 438 if count >= 2*p.F+1 && state.Step == StepTypePreVote { 439 logrus.WithField("IM", p.Id).WithField("hr", p.CurrentHR.String()).Debug("prevote counter is more than 2f+1 #3") 440 p.Broadcast(MessageTypePreCommit, vote.HeightRound, nil, 0) 441 p.changeStep(StepTypePreCommit) 442 } 443 444 } 445 func (p *DefaultPartner) handlePreCommit(commit *MessageCommonVote) { 446 // rule line 47 447 count := p.count(MessageTypePreCommit, commit.HeightRound.Height, commit.HeightRound.Round, MatchTypeAny, "") 448 state := p.States[commit.HeightRound] 449 if count >= 2*p.F+1 && !state.StepTypeEqualPreCommitTriggered { 450 logrus.WithField("IM", p.Id).WithField("hr", commit.HeightRound.String()).Debug("precommit counter is more than 2f+1 #1") 451 state.StepTypeEqualPreCommitTriggered = true 452 p.WaitStepTimeout(StepTypePreCommit, TimeoutPreCommit, commit.HeightRound, p.OnTimeoutPreCommit) 453 } 454 // rule line 49 455 if state.MessageProposal != nil { 456 count = p.count(MessageTypePreCommit, commit.HeightRound.Height, commit.HeightRound.Round, MatchTypeByValue, state.MessageProposal.Value.GetId()) 457 if count >= 2*p.F+1 { 458 if state.Decision == nil { 459 // output decision 460 state.Decision = state.MessageProposal.Value 461 logrus.WithFields(logrus.Fields{ 462 "IM": p.Id, 463 "hr": p.CurrentHR.String(), 464 "value": state.MessageProposal.Value, 465 }).Info("Decision") 466 467 p.StartNewEra(p.CurrentHR.Height+1, 0) 468 } 469 } 470 } 471 472 } 473 474 // valid checks proposal validation 475 // TODO: inject so that valid will call a function to validate the proposal 476 func (p *DefaultPartner) valid(proposal Proposal) bool { 477 return true 478 } 479 480 // count votes and commits from others. 481 func (p *DefaultPartner) count(messageType MessageType, height int, validRound int, valueIdMatchType ValueIdMatchType, valueId string) int { 482 counter := 0 483 var target []*MessageCommonVote 484 state, ok := p.States[HeightRound{ 485 Height: height, 486 Round: validRound, 487 }] 488 if !ok { 489 return 0 490 } 491 switch messageType { 492 case MessageTypePreVote: 493 target = state.PreVotes 494 case MessageTypePreCommit: 495 target = state.PreCommits 496 default: 497 target = nil 498 } 499 for _, m := range target { 500 if m == nil { 501 continue 502 } 503 if m.HeightRound.Height > height || m.HeightRound.Round > validRound { 504 p.dumpAll("impossible now") 505 panic("wrong logic: " + fmt.Sprintf("%d %d %d %d", m.HeightRound.Height, height, m.HeightRound.Round, validRound)) 506 } 507 switch valueIdMatchType { 508 case MatchTypeByValue: 509 if m.Idv == valueId { 510 counter++ 511 } 512 case MatchTypeNil: 513 if m.Idv == "" { 514 counter++ 515 } 516 case MatchTypeAny: 517 counter++ 518 } 519 } 520 logrus.WithField("IM", p.Id). 521 Debugf("Counting: [%d] %s H:%d VR:%d MT:%d", counter, messageType.String(), height, validRound, valueIdMatchType) 522 return counter 523 } 524 525 // checkRound will init all data structure this message needs. 526 // It also check if the message is out of date, or advanced too much 527 func (p *DefaultPartner) checkRound(message *BasicMessage) (needHandle bool) { 528 // rule line 55 529 // slightly changed this so that if there is f+1 newer HeightRound(instead of just round), catch up to this HeightRound 530 if message.HeightRound.IsAfter(p.CurrentHR) { 531 state, ok := p.States[message.HeightRound] 532 if !ok { 533 // create one 534 // TODO: verify if someone is generating garbage height 535 d, c := p.initHeightRound(message.HeightRound) 536 state = d 537 if c != len(p.States) { 538 panic("number not aligned") 539 } 540 } 541 state.Sources[message.SourceId] = true 542 logrus.WithField("IM", p.Id).Tracef("Set source: %d at %s, %+v", message.SourceId, message.HeightRound.String(), state.Sources) 543 logrus.WithField("IM", p.Id).Tracef("%d's %s state is %+v, after receiving message %s from %d", p.Id, p.CurrentHR.String(), p.States[p.CurrentHR].Sources, message.HeightRound.String(), message.SourceId) 544 545 if len(state.Sources) >= p.F+1 { 546 p.dumpAll("New era received") 547 p.StartNewEra(message.HeightRound.Height, message.HeightRound.Round) 548 } 549 } 550 551 return message.HeightRound.IsAfterOrEqual(p.CurrentHR) 552 } 553 554 // changeStep updates the step and then notify the waiter. 555 func (p *DefaultPartner) changeStep(stepType StepType) { 556 p.States[p.CurrentHR].Step = stepType 557 p.waiter.UpdateContext(&TendermintContext{ 558 HeightRound: p.CurrentHR, 559 StepType: stepType, 560 }) 561 } 562 563 // dumpVotes prints all current votes received 564 func (p *DefaultPartner) dumpVotes(votes []*MessageCommonVote) string { 565 sb := strings.Builder{} 566 sb.WriteString("[") 567 for _, vote := range votes { 568 if vote == nil { 569 sb.WriteString(fmt.Sprintf("[nil Vote]")) 570 } else { 571 sb.WriteString(fmt.Sprintf("[%d hr:%s s:%s]", vote.SourceId, vote.HeightRound.String(), vote.Idv)) 572 } 573 574 sb.WriteString(" ") 575 } 576 sb.WriteString("]") 577 return sb.String() 578 } 579 580 func (p *DefaultPartner) dumpAll(reason string) { 581 //return 582 state := p.States[p.CurrentHR] 583 logrus.WithField("IM", p.Id).WithField("hr", p.CurrentHR).WithField("reason", reason).Debug("Dumping") 584 logrus.WithField("IM", p.Id).WithField("hr", p.CurrentHR).WithField("votes", "prevotes").Debug(p.dumpVotes(state.PreVotes)) 585 logrus.WithField("IM", p.Id).WithField("hr", p.CurrentHR).WithField("votes", "precommits").Debug(p.dumpVotes(state.PreCommits)) 586 logrus.WithField("IM", p.Id).WithField("hr", p.CurrentHR).WithField("step", state.Step.String()).Debug("Step") 587 logrus.WithField("IM", p.Id).WithField("hr", p.CurrentHR).Debugf("%+v %d", state.Sources, len(state.Sources)) 588 } 589 590 func (p *DefaultPartner) WipeOldStates() { 591 var toRemove []HeightRound 592 for hr := range p.States { 593 if hr.IsBefore(p.CurrentHR) { 594 toRemove = append(toRemove, hr) 595 } 596 } 597 for _, hr := range toRemove { 598 delete(p.States, hr) 599 } 600 } 601 602 func (p *DefaultPartner) initHeightRound(hr HeightRound) (*HeightRoundState, int) { 603 // first check if there is previous message received 604 if _, ok := p.States[hr]; !ok { 605 // init one 606 p.States[hr] = NewHeightRoundState(p.N) 607 } 608 return p.States[hr], len(p.States) 609 }