fiz: correções da pool

This commit is contained in:
Júnior
2025-06-17 18:26:14 -03:00
parent 682027d517
commit 9259f36e9c
31 changed files with 373 additions and 269 deletions

View File

@ -3,6 +3,7 @@ package api
import (
"net/http"
"strconv"
"github.com/gorilla/mux"
)
@ -12,7 +13,7 @@ func (h *Handler) ListBlocks(w http.ResponseWriter, r *http.Request) {
WriteError(w, http.StatusInternalServerError, "Erro ao listar blocos")
return
}
WriteJSON(w, blocks)
WriteJSON(w, http.StatusOK, blocks)
}
func (h *Handler) GetBlockByHeight(w http.ResponseWriter, r *http.Request) {
@ -27,5 +28,5 @@ func (h *Handler) GetBlockByHeight(w http.ResponseWriter, r *http.Request) {
WriteError(w, http.StatusNotFound, "Bloco não encontrado")
return
}
WriteJSON(w, block)
}
WriteJSON(w, http.StatusOK, block)
}

View File

@ -5,8 +5,9 @@ import (
"net/http"
"strconv"
"github.com/gorilla/mux"
"dejo_node/internal/dao"
"github.com/gorilla/mux"
)
type CreateProposalRequest struct {
@ -30,7 +31,7 @@ func (h *Handler) CreateProposal(w http.ResponseWriter, r *http.Request) {
}
p := DaoStore.Create(req.Title, req.Content, req.Creator, dao.ProposalType(req.Type), req.Duration)
_ = DaoStore.SaveToDisk("data/proposals.db")
WriteJSON(w, p)
WriteJSON(w, http.StatusOK, p)
}
func (h *Handler) VoteProposal(w http.ResponseWriter, r *http.Request) {
@ -53,7 +54,7 @@ func (h *Handler) VoteProposal(w http.ResponseWriter, r *http.Request) {
_ = p.Vote(req.Address, req.Approve)
p.CheckAndClose(h.snapshotStakes())
_ = DaoStore.SaveToDisk("data/proposals.db")
WriteJSON(w, p)
WriteJSON(w, http.StatusOK, p)
}
func (h *Handler) GetProposal(w http.ResponseWriter, r *http.Request) {
@ -68,11 +69,11 @@ func (h *Handler) GetProposal(w http.ResponseWriter, r *http.Request) {
WriteError(w, http.StatusNotFound, "Proposta não encontrada")
return
}
WriteJSON(w, p)
WriteJSON(w, http.StatusOK, p)
}
func (h *Handler) ListProposals(w http.ResponseWriter, r *http.Request) {
WriteJSON(w, DaoStore.List())
WriteJSON(w, http.StatusOK, DaoStore.List())
}
func (h *Handler) snapshotStakes() map[string]uint64 {
@ -81,4 +82,4 @@ func (h *Handler) snapshotStakes() map[string]uint64 {
snapshot[addr] = entry.Amount
}
return snapshot
}
}

View File

@ -1,20 +1,33 @@
package api
import (
"dejo_node/internal/mempool"
"dejo_node/internal/staking"
"dejo_node/internal/storage"
"dejo_node/internal/transactions"
"net/http"
)
type Handler struct {
Store *storage.BlockStore
StakingStore *staking.StakingStore
Mempool *mempool.Mempool
Mempool *transactions.Mempool
}
func NewHandler() *Handler {
return &Handler{
Store: storage.NewBlockStore("data/blocks.json"),
StakingStore: staking.NewStakingStore(),
}
}
return &Handler{}
}
func (h *Handler) SetStores(blockStore *storage.BlockStore, stakingStore *staking.StakingStore, mempool *transactions.Mempool) {
h.Store = blockStore
h.StakingStore = stakingStore
h.Mempool = mempool
}
func (h *Handler) Ping(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("pong"))
}
func (h *Handler) HandleConsensus(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}

View File

@ -2,9 +2,10 @@ package api
import (
"net/http"
"time"
)
func (h *Handler) Health(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("ok"))
}
w.Write([]byte(time.Now().Format(time.RFC3339)))
}

View File

@ -2,41 +2,25 @@ package api
import (
"github.com/gorilla/mux"
"net/http"
)
func NewRouter(h *Handler) http.Handler {
r := mux.NewRouter()
r.HandleFunc("/health", h.Health).Methods("GET")
r.HandleFunc("/startup", h.Startup).Methods("GET")
r.HandleFunc("/ready", h.Ready).Methods("GET")
// ⚡ Transações
r.HandleFunc("/transaction", h.SendTransaction).Methods("POST")
r.HandleFunc("/transaction/{hash}", h.GetTransactionByHash).Methods("GET")
// 🔐 Staking
r.HandleFunc("/stake", h.StakeHandler).Methods("POST")
r.HandleFunc("/unstake", h.UnstakeHandler).Methods("POST")
r.HandleFunc("/stake/{address}", h.GetStakeInfo).Methods("GET")
// 🗳️ DAO
r.HandleFunc("/dao/proposals", h.CreateProposal).Methods("POST")
r.HandleFunc("/dao/proposals/{id}/vote", h.VoteProposal).Methods("POST")
r.HandleFunc("/dao/proposals", h.ListProposals).Methods("GET")
r.HandleFunc("/dao/proposals/{id}", h.GetProposal).Methods("GET")
// 💰 Accounts
r.HandleFunc("/accounts/{address}", HandleGetBalance).Methods("GET")
r.HandleFunc("/accounts/{address}/txs", HandleGetTransactionsByAddress).Methods("GET")
// 📦 Blocos
func (h *Handler) RegisterRoutes(r *mux.Router) {
// Rotas de blocos
r.HandleFunc("/blocks", h.ListBlocks).Methods("GET")
r.HandleFunc("/blocks/{height}", h.GetBlockByHeight).Methods("GET")
// ❤️ Heartbeat
r.HandleFunc("/ping", HandlePing).Methods("GET")
// Rotas de staking
r.HandleFunc("/stake", h.Stake).Methods("POST")
r.HandleFunc("/unstake", h.Unstake).Methods("POST")
r.HandleFunc("/stake-info", h.GetStakeInfo).Methods("GET")
return r
}
// Rotas de transações
r.HandleFunc("/tx", h.SubmitTransaction).Methods("POST")
r.HandleFunc("/tx/{hash}", h.GetTransaction).Methods("GET")
// Rotas de consenso
r.HandleFunc("/consensus", h.HandleConsensus).Methods("POST")
// Health check
r.HandleFunc("/ping", h.Ping).Methods("GET")
}

View File

@ -1,21 +1,19 @@
package api
import (
"github.com/gorilla/mux"
"net/http"
"dejo_node/internal/ws"
"net/http"
"github.com/gorilla/mux"
)
func NewServer(handler *Handler) http.Handler {
r := mux.NewRouter()
// ⚠️ Endpoints removidos temporariamente pois ainda não foram implementados
// r.HandleFunc("/block/latest", handler.GetLatestBlock).Methods("GET")
// r.HandleFunc("/block/{index}", handler.GetBlockByIndex).Methods("GET")
// r.HandleFunc("/tx/{hash}", handler.GetTransactionByHash).Methods("GET")
// r.HandleFunc("/tx", handler.PostTransaction).Methods("POST")
// r.HandleFunc("/oracle/{key}", handler.GetOracleValue).Methods("GET")
// Endpoints básicos
r.HandleFunc("/ping", handler.Ping).Methods("GET")
r.HandleFunc("/consensus", handler.HandleConsensus).Methods("POST")
r.HandleFunc("/ws", ws.HandleWS).Methods("GET")
return r
}
}

View File

@ -1,53 +1,52 @@
package api
import (
"encoding/json"
"net/http"
"github.com/gorilla/mux"
"strconv"
)
type StakeRequest struct {
Address string `json:"address"`
Amount uint64 `json:"amount"`
Duration int64 `json:"duration"`
}
type UnstakeRequest struct {
Address string `json:"address"`
}
func (h *Handler) StakeHandler(w http.ResponseWriter, r *http.Request) {
var req StakeRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
WriteError(w, http.StatusBadRequest, "Invalid request")
return
}
if err := h.StakingStore.Stake(req.Address, req.Amount, uint64(req.Duration)); err != nil {
WriteError(w, http.StatusInternalServerError, "Failed to stake")
return
}
WriteJSON(w, map[string]string{"message": "Stake registered successfully"})
}
func (h *Handler) UnstakeHandler(w http.ResponseWriter, r *http.Request) {
var req UnstakeRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
WriteError(w, http.StatusBadRequest, "Invalid request")
return
}
if err := h.StakingStore.Unstake(req.Address); err != nil {
WriteError(w, http.StatusInternalServerError, "Failed to unstake")
return
}
WriteJSON(w, map[string]string{"message": "Unstake successful"})
}
func (h *Handler) GetStakeInfo(w http.ResponseWriter, r *http.Request) {
address := mux.Vars(r)["address"]
info, ok := h.StakingStore.GetStakeInfo(address)
if !ok {
WriteError(w, http.StatusNotFound, "Stake info not found")
address := r.URL.Query().Get("address")
info, exists := h.StakingStore.GetStakeInfo(address)
if !exists {
WriteError(w, http.StatusNotFound, "Endereço não encontrado")
return
}
WriteJSON(w, info)
}
WriteJSON(w, http.StatusOK, info)
}
func (h *Handler) Stake(w http.ResponseWriter, r *http.Request) {
address := r.URL.Query().Get("address")
amountStr := r.URL.Query().Get("amount")
durationStr := r.URL.Query().Get("duration")
amount, err := strconv.ParseUint(amountStr, 10, 64)
if err != nil {
WriteError(w, http.StatusBadRequest, "Valor inválido")
return
}
duration, err := strconv.ParseUint(durationStr, 10, 64)
if err != nil {
WriteError(w, http.StatusBadRequest, "Duração inválida")
return
}
err = h.StakingStore.Stake(address, amount, duration)
if err != nil {
WriteError(w, http.StatusInternalServerError, "Erro ao realizar stake")
return
}
w.WriteHeader(http.StatusOK)
}
func (h *Handler) Unstake(w http.ResponseWriter, r *http.Request) {
address := r.URL.Query().Get("address")
err := h.StakingStore.Unstake(address)
if err != nil {
WriteError(w, http.StatusInternalServerError, "Erro ao remover stake")
return
}
w.WriteHeader(http.StatusOK)
}

View File

@ -1,30 +1,33 @@
package api
import (
"encoding/json"
"log"
"net/http"
"dejo_node/internal/transactions"
"net/http"
)
func (h *Handler) SendTransaction(w http.ResponseWriter, r *http.Request) {
log.Println("📥 Nova transação recebida")
func (h *Handler) SubmitTransaction(w http.ResponseWriter, r *http.Request) {
var tx transactions.Transaction
decoder := json.NewDecoder(r.Body)
if err := decoder.Decode(&tx); err != nil {
http.Error(w, "formato inválido", http.StatusBadRequest)
err := ReadJSON(r, &tx)
if err != nil {
WriteError(w, http.StatusBadRequest, "Transação inválida")
return
}
if tx.From == "" || tx.To == "" || tx.Value <= 0 {
http.Error(w, "transação inválida", http.StatusBadRequest)
err = h.Mempool.Add(&tx)
if err != nil {
WriteError(w, http.StatusInternalServerError, "Erro ao adicionar transação")
return
}
h.Mempool.Add(&tx)
log.Printf("✅ Transação adicionada ao mempool: %+v\n", tx)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
}
}
func (h *Handler) GetTransaction(w http.ResponseWriter, r *http.Request) {
hash := r.URL.Query().Get("hash")
tx := h.Mempool.GetByHash(hash)
if tx == nil {
WriteError(w, http.StatusNotFound, "Transação não encontrada")
return
}
WriteJSON(w, http.StatusOK, tx)
}

View File

@ -5,18 +5,17 @@ import (
"net/http"
)
func WriteError(w http.ResponseWriter, code int, msg string) {
w.WriteHeader(code)
_ = json.NewEncoder(w).Encode(map[string]interface{}{
"success": false,
"error": msg,
})
func WriteJSON(w http.ResponseWriter, status int, v any) error {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
return json.NewEncoder(w).Encode(v)
}
func WriteJSON(w http.ResponseWriter, data interface{}) {
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(map[string]interface{}{
"success": true,
"data": data,
})
}
func ReadJSON(r *http.Request, v any) error {
return json.NewDecoder(r.Body).Decode(v)
}
func WriteError(w http.ResponseWriter, status int, message string) {
w.WriteHeader(status)
json.NewEncoder(w).Encode(map[string]string{"error": message})
}

View File

@ -16,8 +16,8 @@ const (
PhasePrevote = "PREVOTE"
PhasePrecommit = "PRECOMMIT"
rewardAmount = 5
MaxRoundTimeout = 10 * time.Second
rewardAmount = 5
MaxRoundTimeout = 10 * time.Second
)
func StartConsensusLoop(
@ -66,14 +66,13 @@ func StartConsensusLoop(
log.Println("🛑 Loop de consenso encerrado")
return
case <-ticker.C:
roundState.Mu.Lock()
currentRound := roundState.Round
if time.Since(roundState.LastRoundStart) > MaxRoundTimeout {
log.Println("⏰ Timeout! Reiniciando round", roundState.Round+1)
roundState.ResetRound(roundState.Round + 1)
log.Println("⏰ Timeout! Reiniciando round", currentRound+1)
roundState.ResetRound(currentRound + 1)
roundState.LastRoundStart = time.Now()
phase = PhaseProposal
roundState.Mu.Unlock()
continue
}
@ -95,7 +94,7 @@ func StartConsensusLoop(
BaseMsg: BaseMsg{
MsgType: ProposalType,
HeightVal: roundState.Height,
RoundVal: roundState.Round,
RoundVal: currentRound,
Validator: nodeID,
Time: time.Now(),
},
@ -107,15 +106,17 @@ func StartConsensusLoop(
case PhasePrevote:
log.Println("🗳️ Fase de PREVOTE")
proposalHash := roundState.Proposal
vote := PrevoteMsg{
BaseMsg: BaseMsg{
MsgType: PrevoteType,
HeightVal: roundState.Height,
RoundVal: roundState.Round,
RoundVal: currentRound,
Validator: nodeID,
Time: time.Now(),
},
BlockHash: roundState.Proposal,
BlockHash: proposalHash,
}
roundState.Prevotes[nodeID] = vote.BlockHash
broadcast(vote)
@ -123,15 +124,17 @@ func StartConsensusLoop(
case PhasePrecommit:
log.Println("🔐 Fase de PRECOMMIT")
proposalHash := roundState.Proposal
vote := PrecommitMsg{
BaseMsg: BaseMsg{
MsgType: PrecommitType,
HeightVal: roundState.Height,
RoundVal: roundState.Round,
RoundVal: currentRound,
Validator: nodeID,
Time: time.Now(),
},
BlockHash: roundState.Proposal,
BlockHash: proposalHash,
}
roundState.Precommits[nodeID] = vote.BlockHash
broadcast(vote)
@ -164,11 +167,10 @@ func StartConsensusLoop(
}
ApplySlash(roundState.Precommits, blockHash, stakingStore, validatorSet)
}
roundState.ResetRound(roundState.Round + 1)
roundState.ResetRound(currentRound + 1)
roundState.LastRoundStart = time.Now()
phase = PhaseProposal
}
roundState.Mu.Unlock()
}
}
}
}

View File

@ -1,42 +1,38 @@
package consensus
import (
"sync"
"time"
)
// RoundState mantém o estado atual da altura e rodada de consenso.
type RoundState struct {
Height uint64 // Altura atual do consenso (número do bloco)
Round uint64 // Rodada atual (tentativas por altura)
LockedBlock string // Hash do bloco "travado" (caso tenha precommit anterior)
Proposal string // Hash da proposta atual recebida
Prevotes map[string]string // Mapa[ValidatorID] = BlockHash (pode ser vazio)
Precommits map[string]string // Mapa[ValidatorID] = BlockHash
LastRoundStart time.Time // 🆕 Controle de início da rodada
Mu sync.RWMutex // Proteção de acesso concorrente
Height uint64 // Altura atual do consenso (número do bloco)
Round uint64 // Rodada atual (tentativas por altura)
LockedBlock string // Hash do bloco "travado" (caso tenha precommit anterior)
Proposal string // Hash da proposta atual recebida
Prevotes map[string]string // Mapa[ValidatorID] = BlockHash (pode ser vazio)
Precommits map[string]string // Mapa[ValidatorID] = BlockHash
LastRoundStart time.Time // Controle de início da rodada
}
// NewRoundState cria um estado novo para uma altura específica.
func NewRoundState(height uint64) *RoundState {
return &RoundState{
Height: height,
Round: 0,
LockedBlock: "",
Proposal: "",
Prevotes: make(map[string]string),
Precommits: make(map[string]string),
LastRoundStart: time.Now(),
Height: height,
Round: 0,
LockedBlock: "",
Proposal: "",
Prevotes: make(map[string]string),
Precommits: make(map[string]string),
LastRoundStart: time.Now(),
}
}
// ResetRound limpa os votos e proposta da rodada atual (usado ao iniciar nova rodada).
func (rs *RoundState) ResetRound(round uint64) {
rs.Mu.Lock()
defer rs.Mu.Unlock()
rs.Round = round
rs.Proposal = ""
rs.Prevotes = make(map[string]string)
rs.Precommits = make(map[string]string)
rs.LastRoundStart = time.Now()
}
}

View File

@ -13,8 +13,9 @@ import (
)
type HTTPTransport struct {
peers []string
handlers map[MessageType]func(ConsensusMessage)
peers []string
handlers map[MessageType]func(ConsensusMessage)
receiveChan chan ConsensusMessage // Canal para enviar mensagens recebidas
}
func NewHTTPTransport() *HTTPTransport {
@ -67,32 +68,43 @@ func (t *HTTPTransport) HandleIncoming(w http.ResponseWriter, r *http.Request) {
return
}
var msg ConsensusMessage
switch base.MsgType {
case ProposalType:
var msg ProposalMsg
_ = json.Unmarshal(bodyBytes, &msg)
if h, ok := t.handlers[ProposalType]; ok {
h(msg)
}
msg = &ProposalMsg{}
case PrevoteType:
var msg PrevoteMsg
_ = json.Unmarshal(bodyBytes, &msg)
if h, ok := t.handlers[PrevoteType]; ok {
h(msg)
}
msg = &PrevoteMsg{}
case PrecommitType:
var msg PrecommitMsg
_ = json.Unmarshal(bodyBytes, &msg)
if h, ok := t.handlers[PrecommitType]; ok {
h(msg)
}
msg = &PrecommitMsg{}
default:
log.Println("⚠️ Tipo de mensagem desconhecido:", base.MsgType)
w.WriteHeader(http.StatusBadRequest)
return
}
if err := json.Unmarshal(bodyBytes, msg); err != nil {
log.Println("❌ Erro ao decodificar mensagem:", err)
w.WriteHeader(http.StatusBadRequest)
return
}
select {
case t.receiveChan <- msg:
w.WriteHeader(http.StatusOK)
log.Printf("✅ Mensagem %s recebida de %s\n", base.MsgType, base.Validator)
default:
log.Println("⚠️ Canal de mensagens cheio - descartando mensagem")
w.WriteHeader(http.StatusServiceUnavailable)
}
w.WriteHeader(http.StatusOK)
}
// PingPeer envia um ping para o peer e espera resposta.
func (t *HTTPTransport) Receive() <-chan ConsensusMessage {
ch := make(chan ConsensusMessage, 100)
t.receiveChan = ch // Armazena o canal para uso nos handlers
return ch
}
func (t *HTTPTransport) PingPeer(peer string) error {
client := http.Client{
Timeout: 2 * time.Second,
@ -106,4 +118,4 @@ func (t *HTTPTransport) PingPeer(peer string) error {
return errors.New("resposta inválida ao ping")
}
return nil
}
}

View File

@ -23,3 +23,7 @@ func (m *Mempool) All() []*transactions.Transaction {
func (m *Mempool) Clear() {
m.transactions = []*transactions.Transaction{}
}
func (m *Mempool) GetTransactions() []*transactions.Transaction {
return m.transactions
}