github.com/koko1123/flow-go-1@v0.29.6/consensus/hotstuff/integration/instance_test.go (about) 1 package integration 2 3 import ( 4 "context" 5 "fmt" 6 "sync" 7 "time" 8 9 "github.com/gammazero/workerpool" 10 "github.com/rs/zerolog" 11 "github.com/stretchr/testify/mock" 12 "github.com/stretchr/testify/require" 13 14 "github.com/koko1123/flow-go-1/consensus/hotstuff" 15 "github.com/koko1123/flow-go-1/consensus/hotstuff/blockproducer" 16 "github.com/koko1123/flow-go-1/consensus/hotstuff/eventhandler" 17 "github.com/koko1123/flow-go-1/consensus/hotstuff/forks" 18 "github.com/koko1123/flow-go-1/consensus/hotstuff/forks/finalizer" 19 "github.com/koko1123/flow-go-1/consensus/hotstuff/forks/forkchoice" 20 "github.com/koko1123/flow-go-1/consensus/hotstuff/helper" 21 "github.com/koko1123/flow-go-1/consensus/hotstuff/mocks" 22 "github.com/koko1123/flow-go-1/consensus/hotstuff/model" 23 "github.com/koko1123/flow-go-1/consensus/hotstuff/notifications" 24 "github.com/koko1123/flow-go-1/consensus/hotstuff/pacemaker" 25 "github.com/koko1123/flow-go-1/consensus/hotstuff/pacemaker/timeout" 26 "github.com/koko1123/flow-go-1/consensus/hotstuff/validator" 27 "github.com/koko1123/flow-go-1/consensus/hotstuff/voteaggregator" 28 "github.com/koko1123/flow-go-1/consensus/hotstuff/votecollector" 29 "github.com/koko1123/flow-go-1/consensus/hotstuff/voter" 30 "github.com/koko1123/flow-go-1/model/flow" 31 "github.com/koko1123/flow-go-1/module/irrecoverable" 32 module "github.com/koko1123/flow-go-1/module/mock" 33 msig "github.com/koko1123/flow-go-1/module/signature" 34 "github.com/koko1123/flow-go-1/utils/unittest" 35 ) 36 37 type Instance struct { 38 39 // instance parameters 40 participants flow.IdentityList 41 localID flow.Identifier 42 blockVoteIn VoteFilter 43 blockVoteOut VoteFilter 44 blockPropIn ProposalFilter 45 blockPropOut ProposalFilter 46 stop Condition 47 48 // instance data 49 queue chan interface{} 50 updatingBlocks sync.RWMutex 51 headers map[flow.Identifier]*flow.Header 52 pendings map[flow.Identifier]*model.Proposal // indexed by parent ID 53 54 // mocked dependencies 55 committee *mocks.Committee 56 builder *module.Builder 57 finalizer *module.Finalizer 58 persist *mocks.Persister 59 signer *mocks.Signer 60 verifier *mocks.Verifier 61 communicator *mocks.Communicator 62 63 // real dependencies 64 pacemaker hotstuff.PaceMaker 65 producer *blockproducer.BlockProducer 66 forks *forks.Forks 67 aggregator *voteaggregator.VoteAggregator 68 voter *voter.Voter 69 validator *validator.Validator 70 71 // main logic 72 handler *eventhandler.EventHandler 73 } 74 75 func NewInstance(t require.TestingT, options ...Option) *Instance { 76 77 // generate random default identity 78 identity := unittest.IdentityFixture() 79 80 // initialize the default configuration 81 cfg := Config{ 82 Root: DefaultRoot(), 83 Participants: flow.IdentityList{identity}, 84 LocalID: identity.NodeID, 85 Timeouts: timeout.DefaultConfig, 86 IncomingVotes: BlockNoVotes, 87 OutgoingVotes: BlockNoVotes, 88 IncomingProposals: BlockNoProposals, 89 OutgoingProposals: BlockNoProposals, 90 StopCondition: RightAway, 91 } 92 93 // apply the custom options 94 for _, option := range options { 95 option(&cfg) 96 } 97 98 // check the local ID is a participant 99 var index uint 100 takesPart := false 101 for i, participant := range cfg.Participants { 102 if participant.NodeID == cfg.LocalID { 103 index = uint(i) 104 takesPart = true 105 break 106 } 107 } 108 require.True(t, takesPart) 109 110 // initialize the instance 111 in := Instance{ 112 113 // instance parameters 114 participants: cfg.Participants, 115 localID: cfg.LocalID, 116 blockVoteIn: cfg.IncomingVotes, 117 blockVoteOut: cfg.OutgoingVotes, 118 blockPropIn: cfg.IncomingProposals, 119 blockPropOut: cfg.OutgoingProposals, 120 stop: cfg.StopCondition, 121 122 // instance data 123 pendings: make(map[flow.Identifier]*model.Proposal), 124 headers: make(map[flow.Identifier]*flow.Header), 125 queue: make(chan interface{}, 1024), 126 127 // instance mocks 128 committee: &mocks.Committee{}, 129 builder: &module.Builder{}, 130 persist: &mocks.Persister{}, 131 signer: &mocks.Signer{}, 132 verifier: &mocks.Verifier{}, 133 communicator: &mocks.Communicator{}, 134 finalizer: &module.Finalizer{}, 135 } 136 137 // insert root block into headers register 138 in.headers[cfg.Root.ID()] = cfg.Root 139 140 // program the hotstuff committee state 141 in.committee.On("Identities", mock.Anything).Return( 142 func(blockID flow.Identifier) flow.IdentityList { 143 return in.participants 144 }, 145 nil, 146 ) 147 for _, participant := range in.participants { 148 in.committee.On("Identity", mock.Anything, participant.NodeID).Return(participant, nil) 149 } 150 in.committee.On("Self").Return(in.localID) 151 in.committee.On("LeaderForView", mock.Anything).Return( 152 func(view uint64) flow.Identifier { 153 return in.participants[int(view)%len(in.participants)].NodeID 154 }, nil, 155 ) 156 157 // program the builder module behaviour 158 in.builder.On("BuildOn", mock.Anything, mock.Anything).Return( 159 func(parentID flow.Identifier, setter func(*flow.Header) error) *flow.Header { 160 in.updatingBlocks.Lock() 161 defer in.updatingBlocks.Unlock() 162 163 parent, ok := in.headers[parentID] 164 if !ok { 165 return nil 166 } 167 header := &flow.Header{ 168 ChainID: "chain", 169 ParentID: parentID, 170 Height: parent.Height + 1, 171 PayloadHash: unittest.IdentifierFixture(), 172 Timestamp: time.Now().UTC(), 173 } 174 require.NoError(t, setter(header)) 175 in.headers[header.ID()] = header 176 return header 177 }, 178 func(parentID flow.Identifier, setter func(*flow.Header) error) error { 179 in.updatingBlocks.RLock() 180 _, ok := in.headers[parentID] 181 in.updatingBlocks.RUnlock() 182 if !ok { 183 return fmt.Errorf("parent block not found (parent: %x)", parentID) 184 } 185 return nil 186 }, 187 ) 188 189 // check on stop condition, stop the tests as soon as entering a certain view 190 in.persist.On("PutStarted", mock.Anything).Return(nil) 191 in.persist.On("PutVoted", mock.Anything).Return(nil) 192 193 // program the hotstuff signer behaviour 194 in.signer.On("CreateProposal", mock.Anything).Return( 195 func(block *model.Block) *model.Proposal { 196 proposal := &model.Proposal{ 197 Block: block, 198 SigData: nil, 199 } 200 return proposal 201 }, 202 nil, 203 ) 204 in.signer.On("CreateVote", mock.Anything).Return( 205 func(block *model.Block) *model.Vote { 206 vote := &model.Vote{ 207 View: block.View, 208 BlockID: block.BlockID, 209 SignerID: in.localID, 210 SigData: unittest.RandomBytes(msig.SigLen * 2), // double sig, one staking, one beacon 211 } 212 return vote 213 }, 214 nil, 215 ) 216 in.signer.On("CreateQC", mock.Anything).Return( 217 func(votes []*model.Vote) *flow.QuorumCertificate { 218 voterIDs := make(flow.IdentifierList, 0, len(votes)) 219 for _, vote := range votes { 220 voterIDs = append(voterIDs, vote.SignerID) 221 } 222 223 signerIndices, err := msig.EncodeSignersToIndices(in.participants.NodeIDs(), voterIDs) 224 require.NoError(t, err, "could not encode signer indices") 225 226 qc := &flow.QuorumCertificate{ 227 View: votes[0].View, 228 BlockID: votes[0].BlockID, 229 SignerIndices: signerIndices, 230 SigData: nil, 231 } 232 return qc 233 }, 234 nil, 235 ) 236 237 // program the hotstuff verifier behaviour 238 in.verifier.On("VerifyVote", mock.Anything, mock.Anything, mock.Anything).Return(nil) 239 in.verifier.On("VerifyQC", mock.Anything, mock.Anything, mock.Anything).Return(nil) 240 241 // program the hotstuff communicator behaviour 242 in.communicator.On("BroadcastProposalWithDelay", mock.Anything, mock.Anything).Return( 243 func(header *flow.Header, delay time.Duration) error { 244 245 // sender should always have the parent 246 in.updatingBlocks.RLock() 247 parent, exists := in.headers[header.ParentID] 248 in.updatingBlocks.RUnlock() 249 250 if !exists { 251 return fmt.Errorf("parent for proposal not found (sender: %x, parent: %x)", in.localID, header.ParentID) 252 } 253 254 // set the height and chain ID 255 header.ChainID = parent.ChainID 256 header.Height = parent.Height + 1 257 258 // convert into proposal immediately 259 proposal := model.ProposalFromFlow(header, parent.View) 260 261 // store locally and loop back to engine for processing 262 in.ProcessBlock(proposal) 263 264 return nil 265 }, 266 ) 267 in.communicator.On("SendVote", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) 268 269 // program the finalizer module behaviour 270 in.finalizer.On("MakeFinal", mock.Anything).Return( 271 func(blockID flow.Identifier) error { 272 273 // as we don't use mocks to assert expectations, but only to 274 // simulate behaviour, we should drop the call data regularly 275 in.updatingBlocks.RLock() 276 block, found := in.headers[blockID] 277 in.updatingBlocks.RUnlock() 278 if !found { 279 return fmt.Errorf("can't broadcast with unknown parent") 280 } 281 if block.Height%100 == 0 { 282 in.committee.Calls = nil 283 in.builder.Calls = nil 284 in.signer.Calls = nil 285 in.verifier.Calls = nil 286 in.communicator.Calls = nil 287 in.finalizer.Calls = nil 288 } 289 290 // check on stop condition 291 // TODO: we can remove that once the single instance stop 292 // recursively calling into itself 293 if in.stop(&in) { 294 return errStopCondition 295 } 296 297 return nil 298 }, 299 ) 300 301 in.finalizer.On("MakeValid", mock.Anything).Return(nil) 302 303 // initialize error handling and logging 304 var err error 305 zerolog.TimestampFunc = func() time.Time { return time.Now().UTC() } 306 // log with node index an ID 307 log := unittest.Logger().With(). 308 Int("index", int(index)). 309 Hex("node_id", in.localID[:]). 310 Logger() 311 notifier := notifications.NewLogConsumer(log) 312 313 // initialize the pacemaker 314 controller := timeout.NewController(cfg.Timeouts) 315 in.pacemaker, err = pacemaker.New(DefaultStart(), controller, notifier) 316 require.NoError(t, err) 317 318 // initialize the block producer 319 in.producer, err = blockproducer.New(in.signer, in.committee, in.builder) 320 require.NoError(t, err) 321 322 // initialize the finalizer 323 rootBlock := model.BlockFromFlow(cfg.Root, 0) 324 325 signerIndices, err := msig.EncodeSignersToIndices(in.participants.NodeIDs(), in.participants.NodeIDs()) 326 require.NoError(t, err, "could not encode signer indices") 327 328 rootQC := &flow.QuorumCertificate{ 329 View: rootBlock.View, 330 BlockID: rootBlock.BlockID, 331 SignerIndices: signerIndices, 332 } 333 rootBlockQC := &forks.BlockQC{Block: rootBlock, QC: rootQC} 334 forkalizer, err := finalizer.New(rootBlockQC, in.finalizer, notifier) 335 require.NoError(t, err) 336 337 // initialize the forks choice 338 choice, err := forkchoice.NewNewestForkChoice(forkalizer, notifier) 339 require.NoError(t, err) 340 341 // initialize the forks handler 342 in.forks = forks.New(forkalizer, choice) 343 344 // initialize the validator 345 in.validator = validator.New(in.committee, in.forks, in.verifier) 346 347 weight := uint64(flow.DefaultInitialWeight) 348 stakingSigAggtor := helper.MakeWeightedSignatureAggregator(weight) 349 stakingSigAggtor.On("Verify", mock.Anything, mock.Anything).Return(nil).Maybe() 350 351 rbRector := helper.MakeRandomBeaconReconstructor(msig.RandomBeaconThreshold(int(in.participants.Count()))) 352 rbRector.On("Verify", mock.Anything, mock.Anything).Return(nil).Maybe() 353 354 indices, err := msig.EncodeSignersToIndices(in.participants.NodeIDs(), []flow.Identifier(in.participants.NodeIDs())) 355 require.NoError(t, err) 356 357 packer := &mocks.Packer{} 358 packer.On("Pack", mock.Anything, mock.Anything).Return(indices, unittest.RandomBytes(128), nil).Maybe() 359 360 onQCCreated := func(qc *flow.QuorumCertificate) { 361 in.queue <- qc 362 } 363 364 minRequiredWeight := hotstuff.ComputeWeightThresholdForBuildingQC(uint64(in.participants.Count()) * weight) 365 voteProcessorFactory := &mocks.VoteProcessorFactory{} 366 voteProcessorFactory.On("Create", mock.Anything, mock.Anything).Return( 367 func(log zerolog.Logger, proposal *model.Proposal) hotstuff.VerifyingVoteProcessor { 368 return votecollector.NewCombinedVoteProcessor( 369 log, proposal.Block, 370 stakingSigAggtor, rbRector, 371 onQCCreated, 372 packer, 373 minRequiredWeight, 374 ) 375 }, nil) 376 377 createCollectorFactoryMethod := votecollector.NewStateMachineFactory(log, notifier, voteProcessorFactory.Create) 378 voteCollectors := voteaggregator.NewVoteCollectors(log, DefaultPruned(), workerpool.New(2), createCollectorFactoryMethod) 379 380 // initialize the vote aggregator 381 in.aggregator, err = voteaggregator.NewVoteAggregator(log, notifier, DefaultPruned(), voteCollectors) 382 require.NoError(t, err) 383 384 // initialize the voter 385 in.voter = voter.New(in.signer, in.forks, in.persist, in.committee, DefaultVoted()) 386 387 // initialize the event handler 388 in.handler, err = eventhandler.NewEventHandler(log, in.pacemaker, in.producer, in.forks, in.persist, in.communicator, in.committee, in.aggregator, in.voter, in.validator, notifier) 389 require.NoError(t, err) 390 391 return &in 392 } 393 394 func (in *Instance) Run() error { 395 ctx, cancel := context.WithCancel(context.Background()) 396 defer func() { 397 cancel() 398 <-in.aggregator.Done() 399 }() 400 signalerCtx, _ := irrecoverable.WithSignaler(ctx) 401 in.aggregator.Start(signalerCtx) 402 <-in.aggregator.Ready() 403 404 // start the event handler 405 err := in.handler.Start() 406 if err != nil { 407 return fmt.Errorf("could not start event handler: %w", err) 408 } 409 410 // run until an error or stop condition is reached 411 for { 412 413 // check on stop conditions 414 if in.stop(in) { 415 return errStopCondition 416 } 417 418 // we handle timeouts with priority 419 select { 420 case <-in.handler.TimeoutChannel(): 421 err := in.handler.OnLocalTimeout() 422 if err != nil { 423 return fmt.Errorf("could not process timeout: %w", err) 424 } 425 default: 426 } 427 428 // check on stop conditions 429 if in.stop(in) { 430 return errStopCondition 431 } 432 433 // otherwise, process first received event 434 select { 435 case <-in.handler.TimeoutChannel(): 436 err := in.handler.OnLocalTimeout() 437 if err != nil { 438 return fmt.Errorf("could not process timeout: %w", err) 439 } 440 case msg := <-in.queue: 441 switch m := msg.(type) { 442 case *model.Proposal: 443 err := in.handler.OnReceiveProposal(m) 444 if err != nil { 445 return fmt.Errorf("could not process proposal: %w", err) 446 } 447 case *model.Vote: 448 in.aggregator.AddVote(m) 449 case *flow.QuorumCertificate: 450 err := in.handler.OnQCConstructed(m) 451 if err != nil { 452 return fmt.Errorf("could not process created qc: %w", err) 453 } 454 } 455 } 456 457 } 458 } 459 460 func (in *Instance) ProcessBlock(proposal *model.Proposal) { 461 in.updatingBlocks.Lock() 462 _, parentExists := in.headers[proposal.Block.QC.BlockID] 463 defer in.updatingBlocks.Unlock() 464 465 if parentExists { 466 next := proposal 467 for next != nil { 468 in.headers[next.Block.BlockID] = model.ProposalToFlow(next) 469 470 in.queue <- next 471 // keep processing the pending blocks 472 next = in.pendings[next.Block.QC.BlockID] 473 } 474 } else { 475 // cache it in pendings by ParentID 476 in.pendings[proposal.Block.QC.BlockID] = proposal 477 } 478 }