package consensus import ( "context" "dejo_node/internal/mempool" "dejo_node/internal/staking" "dejo_node/internal/state" "dejo_node/internal/storage" "dejo_node/internal/transactions" "log" "time" ) const ( PhaseProposal = "PROPOSAL" PhasePrevote = "PREVOTE" PhasePrecommit = "PRECOMMIT" rewardAmount = 5 MaxRoundTimeout = 10 * time.Second ) func StartConsensusLoop( ctx context.Context, nodeID string, roundState *RoundState, broadcast func(msg ConsensusMessage), totalValidators int, store *storage.BlockStore, createBlockFn func() *transactions.Block, stakingStore *staking.StakingStore, minStake uint64, liveness *LivenessMonitor, ) { log.Println("🚀 Iniciando loop de consenso para altura", roundState.Height) if store == nil { log.Fatal("❌ ERRO: BlockStore está nil no StartConsensusLoop") } phase := PhaseProposal ticker := time.NewTicker(2 * time.Second) defer ticker.Stop() var proposedBlock *transactions.Block validatorSet := NewValidatorSetFromStaking(stakingStore, minStake) globalState := state.NewState() _ = globalState.LoadFromDisk("data/state.gob") pool := mempool.NewMempool() if !validatorSet.IsValidator(nodeID) { log.Println("⚠️ Este nó não é validador ativo — encerrando consenso nesta altura") return } proposer := validatorSet.SelectProposer(roundState.Height) if proposer == nil { log.Println("❌ Nenhum propositor válido encontrado") return } roundState.LastRoundStart = time.Now() for { select { case <-ctx.Done(): log.Println("🛑 Loop de consenso encerrado") return case <-ticker.C: roundState.Mu.Lock() if time.Since(roundState.LastRoundStart) > MaxRoundTimeout { log.Println("⏰ Timeout! Reiniciando round", roundState.Round+1) roundState.ResetRound(roundState.Round + 1) roundState.LastRoundStart = time.Now() phase = PhaseProposal roundState.Mu.Unlock() continue } switch phase { case PhaseProposal: if proposer.Address != nodeID { log.Println("⏳ Aguardando proposta do propositor", proposer.Address) phase = PhasePrevote break } log.Println("📤 Fase de PROPOSTA - propondo bloco") latest, err := store.GetLatestBlock() if err != nil { log.Println("⚠️ Nenhum bloco encontrado, utilizando bloco base") latest = &transactions.Block{Index: 0, Hash: "genesis"} } proposedBlock = ProposeBlock(uint64(latest.Index+1), latest.Hash, pool) proposal := ProposalMsg{ BaseMsg: BaseMsg{ MsgType: ProposalType, HeightVal: roundState.Height, RoundVal: roundState.Round, Validator: nodeID, Time: time.Now(), }, BlockHash: proposedBlock.Hash, } roundState.Proposal = proposedBlock.Hash broadcast(proposal) phase = PhasePrevote case PhasePrevote: log.Println("🗳️ Fase de PREVOTE") vote := PrevoteMsg{ BaseMsg: BaseMsg{ MsgType: PrevoteType, HeightVal: roundState.Height, RoundVal: roundState.Round, Validator: nodeID, Time: time.Now(), }, BlockHash: roundState.Proposal, } roundState.Prevotes[nodeID] = vote.BlockHash broadcast(vote) phase = PhasePrecommit case PhasePrecommit: log.Println("🔐 Fase de PRECOMMIT") vote := PrecommitMsg{ BaseMsg: BaseMsg{ MsgType: PrecommitType, HeightVal: roundState.Height, RoundVal: roundState.Round, Validator: nodeID, Time: time.Now(), }, BlockHash: roundState.Proposal, } roundState.Precommits[nodeID] = vote.BlockHash broadcast(vote) quorumReached, blockHash := CheckQuorumWeighted(roundState.Precommits, validatorSet, liveness) if quorumReached { log.Println("🎉 Quórum alcançado! Bloco finalizado:", blockHash) if nodeID == proposer.Address && proposedBlock != nil && proposedBlock.Hash == blockHash { totalVotedStake := uint64(0) votedStakes := make(map[string]uint64) for validatorID, votedHash := range roundState.Precommits { if votedHash == blockHash && validatorSet.IsValidator(validatorID) { val, _ := validatorSet.ValidatorByAddress(validatorID) votedStakes[validatorID] = val.Stake totalVotedStake += val.Stake } } for validatorID, stake := range votedStakes { reward := (stake * rewardAmount) / totalVotedStake globalState.Mint(validatorID, reward) log.Printf("💸 Recompensa dinâmica de %d tokens para %s\n", reward, validatorID) } _ = globalState.SaveToDisk("data/state.gob") if err := store.SaveBlock(proposedBlock); err != nil { log.Println("❌ Erro ao persistir bloco:", err) } else { log.Println("💾 Bloco persistido com sucesso! Height:", proposedBlock.Index) pool.Clear() } } ApplySlash(roundState.Precommits, blockHash, stakingStore, validatorSet) } roundState.ResetRound(roundState.Round + 1) roundState.LastRoundStart = time.Now() phase = PhaseProposal } roundState.Mu.Unlock() } } }