commit inicial do projeto
This commit is contained in:
174
internal/consensus/round_loop.go
Normal file
174
internal/consensus/round_loop.go
Normal file
@ -0,0 +1,174 @@
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user