Files
dejo-node/internal/consensus/round_loop.go
2025-05-23 10:44:32 -03:00

174 lines
5.0 KiB
Go

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()
}
}
}