commit inicial do projeto

This commit is contained in:
Júnior
2025-05-23 10:44:32 -03:00
commit 8f04473c0b
106 changed files with 5673 additions and 0 deletions

23
Makefile Normal file
View File

@ -0,0 +1,23 @@
# Makefile para gerenciamento do DEJO Node
.PHONY: build run test docker clean
build:
@echo "🔧 Compilando o DEJO Node..."
go build -o dejo-node ./cmd/main.go
run: build
@echo "🚀 Executando o DEJO Node..."
./dejo-node
test:
@echo "🧪 Executando testes..."
go test ./...
docker:
@echo "🐳 Criando imagem Docker..."
docker build -t dejo-node:latest ./build/
clean:
@echo "🧹 Limpando arquivos temporários..."
rm -f dejo-node

71
README.md Normal file
View File

@ -0,0 +1,71 @@
# 🏗️ DEJO Node - Core da Blockchain
## 📌 Visão Geral
O **DEJO Node** é o núcleo da **blockchain DEJO**, responsável por **validação de transações, consenso, geração de blocos e armazenamento descentralizado**. Ele garante **segurança, escalabilidade e integridade** dos dados na rede.
Para detalhes técnicos e implementação, veja a **documentação completa**:
🔹 **[Arquitetura do Sistema](docs/architecture.md)**
🔹 **[Mecanismo de Consenso](docs/consensus.md)**
🔹 **[API RPC/WebSockets](docs/api.md)**
🔹 **[Healthchecks](docs/healthchecks.md)**
🔹 **[Logging e Auditoria](docs/logging.md)**
🔹 **[Deploy no Kubernetes](docs/kubernetes.md)**
🔹 **[Planejamento de Desenvolvimento](planning.md)**
---
## 🎯 Funcionalidades Principais
**Validação de transações** com criptografia de chave pública.
**Criação e sincronização de blocos** entre os nós.
**Implementação do consenso PoS/BFT** para garantir segurança.
**Persistência de dados com LevelDB/BadgerDB** para escalabilidade.
**API RPC/WebSockets** para interação com a rede.
**Proteção contra ataques Sybil, DDoS e replay attacks**.
**Mecanismo de auditoria on-chain para monitoramento da rede**.
**Criptografia pós-quântica para garantir segurança futura**.
**Staking e Governança** são gerenciados no microserviço [`dejo-governance`](../dejo-governance/README.md).
---
## 🚀 Como Rodar o DEJO Node
```sh
# Clonar o repositório
git clone https://github.com/dejo/dejo_node.git
cd dejo_node
# Instalar dependências
go mod tidy
# Rodar um nó da blockchain
go run cmd/main.go
```
---
## 🔬 Testes e Validação
Os testes cobrem validação de blocos, execução de transações e sincronização P2P.
```sh
# Rodar testes unitários
go test ./...
```
**📌 Testes adicionais planejados:**
- Testes de stress para verificar escalabilidade.
- Simulação de ataques para validar segurança.
- Recuperação após falha para garantir robustez.
---
## 📖 Documentação e Referências
- [Whitepaper Técnico da DEJO](../Whitepaper_Tecnico.pdf)
- [Plano de Negócios - DEJO Digital Assets](../Plano_de_Negocios.pdf)
🚀 **DEJO Node: O Coração da Blockchain!** 🔥

22
build/Dockerfile Normal file
View File

@ -0,0 +1,22 @@
# Dockerfile para o DEJO Node
FROM golang:1.20 as builder
WORKDIR /app
# Copiar arquivos e instalar dependências
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN go build -o dejo-node ./cmd/main.go
# Criar imagem final
FROM debian:bullseye-slim
WORKDIR /root/
COPY --from=builder /app/dejo-node ./dejo-node
# Definir variáveis de ambiente padrão
ENV CONFIG_PATH="/config/config.yaml"
CMD ["./dejo-node"]

50
changelog.md Normal file
View File

@ -0,0 +1,50 @@
# 📦 Changelog - DEJO Node
## ✅ Versão 1.0.0 (Finalizada)
### 🏗️ Estrutura Inicial
- Organização de diretórios `cmd/`, `internal/`, `pkg/`
- Dockerfile, Makefile e configs prontos
- `main.go` funcional
### 🔐 Transações
- Estrutura básica de TX (`from`, `to`, `value`, `nonce`, `signature`)
- Validações e verificação de assinatura
- Mempool integrada
### 📦 Armazenamento
- Implementação com LevelDB
- Indexação por hash e blocos
### 🌐 Comunicação P2P
- libp2p com detecção automática
- Proteção Sybil (limite de conexões, delay por PeerID)
- Logs de conexões e desconexões
### ⚖️ Consenso
- Algoritmo mínimo viável tipo PoA/BFT
- Validação e finalização de blocos
### 🔗 API REST
- Endpoints: `/tx`, `/tx/{hash}`, `/block/{hash}`, `/mempool`
- Health checks: `/health`, `/startup`, `/ready`
### 🗳️ Governança
- Estrutura de staking e votação
- RPCs para propostas e eleição
### 🛰️ Oráculos
- Placeholder e endpoint para feed externo validado
### 🔐 Segurança
- Rate Limiting anti-DDoS
- Monitoramento de conexões suspeitas
- Modularização para reputação de peer
### 📊 DevOps
- `/metrics` com Prometheus (`tx_total`, `uptime`)
- `openapi.yaml` com documentação completa
---
🚀 Projeto DEJO Node pronto para produção modular!

23
changelog.txt Normal file
View File

@ -0,0 +1,23 @@
# 📜 DEJO Node — Changelog Técnico
## [2025-04-27] 🚀 Multi-node DEJO Chain com Consenso Distribuído via HTTP
### ✅ Funcionalidades entregues:
#### 🔥 Comunicação real entre nós
- Implementação de transporte HTTP (`HTTPTransport`) para Broadcast de mensagens de consenso (Proposal, Prevote, Precommit)
- Cada nó expõe `/consensus` para receber mensagens
- Lista de peers (`DEJO_PEERS`) via variável de ambiente
#### 🔁 Multi-node funcional
- Auto-stake automático para todos os nós se necessário
- Consenso BFT ponderado rodando entre múltiplos validadores
- Blocos finalizados em conjunto e persistidos
#### 🧰 Scripts de inicialização
- `scripts/start-node-A.sh`
- `scripts/start-node-B.sh`
- Facilita testes locais e simulação de rede
---
Próximo desafio sugerido: Resiliência e Liveness Detection entre os peers 🚀

73
cmd/main.go Normal file
View File

@ -0,0 +1,73 @@
package main
import (
"context"
"dejo_node/internal/api"
"dejo_node/internal/consensus"
"dejo_node/internal/mempool"
"dejo_node/internal/monitor"
"dejo_node/internal/staking"
"dejo_node/internal/state"
"dejo_node/internal/storage"
"dejo_node/internal/transactions"
"fmt"
"net/http"
"os"
"strings"
"time"
)
func main() {
ctx := context.Background()
nodeID := os.Getenv("NODE_ID")
port := os.Getenv("DEJO_PORT")
validators := strings.Split(os.Getenv("DEJO_VALIDATORS"), ",")
peers := strings.Split(os.Getenv("DEJO_PEERS"), ",")
blockStore := storage.NewBlockStore()
stakingStore := staking.NewStakingStore()
mp := mempool.NewMempool()
globalState := state.NewState()
// Heartbeat function
pingPeer := func(peer string) bool {
client := http.Client{Timeout: 2 * time.Second}
resp, err := client.Get(peer + "/health")
if err != nil {
return false
}
defer resp.Body.Close()
return resp.StatusCode == http.StatusOK
}
monitor := monitor.NewMonitor(pingPeer)
handler := api.NewHandler(blockStore, stakingStore, mp, globalState)
server := api.NewServer(handler)
go func() {
fmt.Printf("🌐 Servidor HTTP iniciado na porta :%s\n", port)
http.ListenAndServe(":"+port, server)
}()
round := consensus.NewRoundState(1)
transport := consensus.NewHTTPTransport()
consensus.StartConsensusLoop(
ctx,
nodeID,
round,
transport.Receive,
700,
blockStore,
func() *transactions.Block {
return &transactions.Block{
Txns: mp.GetTransactions(),
}
},
stakingStore,
globalState,
monitor,
)
}

10
config/config.yaml Normal file
View File

@ -0,0 +1,10 @@
# Configuração do DEJO Node
environment: production
log_level: info
consensus_algorithm: PoS
rpc_port: 8545
ws_port: 8546
database:
type: leveldb
path: "/data/blockchain"

View File

@ -0,0 +1,8 @@
{
"Index": 1,
"PrevHash": "genesis",
"Txns": [],
"Timestamp": 1746203588,
"Nonce": 0,
"Hash": "176b199bd82d6e6d22719cdc95709646433204f299b2f49efbba9985ee9eac4e"
}

View File

@ -0,0 +1,8 @@
{
"Index": 2,
"PrevHash": "176b199bd82d6e6d22719cdc95709646433204f299b2f49efbba9985ee9eac4e",
"Txns": [],
"Timestamp": 1746203596,
"Nonce": 0,
"Hash": "0af7272a293becc055d79604b96717e06be4e3ff8be65f1d65b6d1979b9d166c"
}

View File

@ -0,0 +1,8 @@
{
"Index": 3,
"PrevHash": "0af7272a293becc055d79604b96717e06be4e3ff8be65f1d65b6d1979b9d166c",
"Txns": [],
"Timestamp": 1746206836,
"Nonce": 0,
"Hash": "40186b92c0be8c1cd252b223dce51df3156a3fadd32e21f38db298e01da70f69"
}

View File

@ -0,0 +1,8 @@
{
"Index": 4,
"PrevHash": "40186b92c0be8c1cd252b223dce51df3156a3fadd32e21f38db298e01da70f69",
"Txns": [],
"Timestamp": 1746206844,
"Nonce": 0,
"Hash": "4519522e80850a467c571eccdbad3a9b078b75d0dd3c889a87725c207eff3464"
}

BIN
data/staking.db Normal file

Binary file not shown.

BIN
data/state.gob Normal file

Binary file not shown.

95
docs/api.md Normal file
View File

@ -0,0 +1,95 @@
# 🌐 **Documentação da API - DEJO Node**
## 📌 **Visão Geral**
O **DEJO Node** expõe uma API RPC/WebSockets para permitir **interação com a blockchain**, incluindo envio de transações, consulta de blocos e eventos em tempo real.
---
## **1⃣ Endpoints REST**
### **1.1 - Consultar um Bloco**
**GET /block/{id}**
- Retorna informações sobre um bloco específico.
📌 **Exemplo de resposta:**
```json
{
"blockHeight": 10234,
"hash": "0xa7b9c...",
"previousHash": "0x9f8a...",
"timestamp": 1712456789,
"transactions": ["0xabc123", "0xdef456"]
}
```
### **1.2 - Enviar uma Transação**
**POST /tx**
- Envia uma nova transação para a rede.
📌 **Exemplo de requisição:**
```json
{
"from": "0x123...",
"to": "0xabc...",
"value": 100,
"gas": 21000,
"signature": "0x9f8a..."
}
```
📌 **Exemplo de resposta:**
```json
{
"status": "pending",
"txHash": "0xdef789..."
}
```
### **1.3 - Consultar Status de uma Transação**
**GET /tx/{hash}**
- Retorna o status de uma transação específica.
📌 **Exemplo de resposta:**
```json
{
"txHash": "0xdef789...",
"status": "confirmed",
"blockHeight": 10235
}
```
---
## **2⃣ WebSockets para Eventos em Tempo Real**
### **2.1 - Subscribing em Novos Blocos**
**WS /events**
- Notifica sempre que um novo bloco é minerado.
📌 **Exemplo de mensagem recebida:**
```json
{
"event": "newBlock",
"blockHeight": 10236,
"hash": "0xbbc345..."
}
```
### **2.2 - Subscribing em Transações Confirmadas**
**WS /events**
- Notifica quando uma transação específica é confirmada.
📌 **Exemplo de mensagem recebida:**
```json
{
"event": "txConfirmed",
"txHash": "0xdef789...",
"blockHeight": 10235
}
```
---
📌 **Este documento será atualizado conforme novos endpoints forem adicionados.**
🚀 **DEJO Node: API eficiente para interação com a blockchain!** 🔥

37
docs/architecture.md Normal file
View File

@ -0,0 +1,37 @@
# 🏗️ **Arquitetura do DEJO Node**
## 📌 **Visão Geral**
O **DEJO Node** é o componente central da blockchain DEJO, responsável por **processar transações, validar blocos, manter consenso, armazenar dados e fornecer uma API para interação externa**. Sua arquitetura modular foi projetada para garantir **segurança, escalabilidade e interoperabilidade**.
---
## **1⃣ Componentes Principais**
### **1.1 - Camada de Consenso**
- Implementa **Proof-of-Stake (PoS)** ou **Byzantine Fault Tolerance (BFT)** para validar blocos e garantir a integridade da rede.
- Gerencia **eleição de validadores** e mecanismo de **staking**.
- Inclui penalização (slashing) para validadores maliciosos.
### **1.2 - Camada de Execução de Transações**
- Responsável por **verificar, validar e executar transações**.
- Garante que apenas transações assinadas e válidas sejam incluídas nos blocos.
- Mantém um **mempool** para armazenar transações pendentes.
### **1.3 - Armazenamento e Estado**
- **LevelDB/BadgerDB** para armazenamento persistente de blocos e estados de conta.
- Suporte a **indexação rápida** para consultas eficientes.
- Implementação de **snapshots periódicos** para recuperação rápida da blockchain.
### **1.4 - Comunicação P2P**
- Usa **libp2p** para **sincronização de blocos e transações** entre nós.
- Implementa **descoberta dinâmica de peers** e mecanismos de propagação segura.
### **1.5 - API RPC/WebSockets**
- Disponibiliza **endpoints para consulta e envio de transações**.
- Implementa **WebSockets** para notificações em tempo real.
- Garante **autenticação e controle de acesso** para maior segurança.
---
📌 **Este documento será atualizado conforme novos módulos forem implementados.**
🚀 **DEJO Node: Arquitetura robusta e preparada para o futuro!** 🔥

61
docs/consensus.md Normal file
View File

@ -0,0 +1,61 @@
# ⚖️ Mecanismo de Consenso no DEJO Node
## 📌 Visão Geral
O **DEJO Node** utiliza um mecanismo de consenso híbrido, suportando **Proof-of-Stake (PoS)** e **Byzantine Fault Tolerance (BFT)**. O objetivo é garantir **segurança, escalabilidade e eficiência** na validação dos blocos e transações.
---
## 🔹 Modelos de Consenso Suportados
### **1. Proof-of-Stake (PoS)**
- Validadores são escolhidos com base na **quantidade de tokens em staking**.
- Mais stake = maior chance de ser escolhido para validar blocos.
- Implementação de **mecanismo de penalização (slashing)** para validadores maliciosos.
- Finalização de blocos sem necessidade de mineração intensiva.
### **2. Byzantine Fault Tolerance (BFT)**
- Algoritmo que permite consenso mesmo com **até 1/3 dos nós maliciosos**.
- Comunicação direta entre validadores para garantir consenso.
- Tempo de confirmação de bloco reduzido.
- Melhor aplicabilidade para redes permissionadas.
---
## 🔄 Processo de Validação de Blocos
1. Novas transações são propagadas via **rede P2P**.
2. Nós validadores selecionam um conjunto de transações e montam um bloco.
3. O bloco é proposto e enviado para os outros validadores.
4. Dependendo do modelo de consenso:
- **PoS** → O validador líder assina e propaga o bloco.
- **BFT** → Todos os validadores participam da decisão.
5. Após o consenso, o bloco é **finalizado e registrado no banco de dados**.
6. A blockchain se mantém sincronizada via comunicação P2P.
---
## ⚠️ Segurança e Penalizações (Slashing)
Para evitar ataques e validar a integridade da rede, aplicamos penalizações:
- **Slashing parcial**: Redução de stake caso um validador tente validar blocos conflitantes.
- **Slashing total**: Remoção completa do validador em casos de fraude comprovada.
- **Lista negra**: Impedimento de participação futura de validadores maliciosos.
---
## 📂 Estrutura de Diretórios Relacionada
```
dejo-node/
│── internal/
│ ├── consensus/ # Implementação do consenso (PoS/BFT)
│ ├── transactions/ # Processamento de transações
│ ├── storage/ # Persistência dos blocos
│── tests/
│ ├── consensus/ # Testes unitários e de integração do consenso
```
---
## 📖 Referências
- [Whitepaper Técnico da DEJO](../Whitepaper_Tecnico.pdf)
- [Plano de Negócios - DEJO Digital Assets](../Plano_de_Negocios.pdf)
- [Arquitetura do DEJO Node](architecture.md)
🚀 **DEJO Node: Segurança e Consenso para a Blockchain!** 🔥

108
docs/healthchecks.md Normal file
View File

@ -0,0 +1,108 @@
# 🔍 **Healthchecks - DEJO Node**
## 📌 **Visão Geral**
Os healthchecks do **DEJO Node** garantem que a aplicação está rodando corretamente e que seus componentes essenciais estão operacionais. Eles são divididos em três categorias principais:
1**Liveness Probe** → Verifica se o processo ainda está ativo.
2**Readiness Probe** → Indica se o serviço pode receber tráfego.
3**Startup Probe** → Confirma se a aplicação inicializou corretamente.
---
## **1⃣ Endpoints de Healthcheck**
### **1.1 - Liveness Probe**
📌 **Endpoint:** `GET /health/liveness`
- Retorna se o processo do nó está ativo e não travou.
- Se falhar, o Kubernetes pode reiniciar o pod.
📌 **Exemplo de resposta:**
```json
{
"status": "ok",
"message": "Node is alive",
"timestamp": 1712456789
}
```
---
### **1.2 - Readiness Probe**
📌 **Endpoint:** `GET /health/readiness`
- Verifica se o nó está pronto para receber tráfego.
- Avalia conexões com banco de dados, consenso e rede P2P.
📌 **Exemplo de resposta (sucesso):**
```json
{
"status": "ready",
"dependencies": {
"database": "ok",
"consensus": "ok",
"p2p": "ok"
},
"timestamp": 1712456789
}
```
📌 **Exemplo de resposta (falha):**
```json
{
"status": "not_ready",
"dependencies": {
"database": "error",
"consensus": "ok",
"p2p": "ok"
},
"timestamp": 1712456789
}
```
---
### **1.3 - Startup Probe**
📌 **Endpoint:** `GET /health/startup`
- Indica se o nó foi iniciado corretamente.
- Útil para evitar que o Kubernetes mate o pod prematuramente.
📌 **Exemplo de resposta:**
```json
{
"status": "initialized",
"message": "Node startup completed",
"timestamp": 1712456789
}
```
---
## **2⃣ Configuração no Kubernetes**
📌 **Exemplo de configuração no `deployment.yaml`**:
```yaml
livenessProbe:
httpGet:
path: /health/liveness
port: 8080
initialDelaySeconds: 3
periodSeconds: 5
readinessProbe:
httpGet:
path: /health/readiness
port: 8080
initialDelaySeconds: 10
periodSeconds: 10
startupProbe:
httpGet:
path: /health/startup
port: 8080
failureThreshold: 30
periodSeconds: 10
```
---
📌 **Este documento será atualizado conforme a evolução do serviço.**
🚀 **DEJO Node: Garantindo disponibilidade com healthchecks inteligentes!** 🔥

131
docs/kubernetes.md Normal file
View File

@ -0,0 +1,131 @@
# ☁️ **Deploy no Kubernetes - DEJO Node**
## 📌 **Visão Geral**
Este documento descreve como implantar e gerenciar o **DEJO Node** em um ambiente Kubernetes, garantindo **alta disponibilidade, escalabilidade e monitoramento eficiente**.
---
## **1⃣ Estrutura de Deploy**
### **1.1 - Componentes no Kubernetes**
- **Deployment**: Gerencia os pods do DEJO Node.
- **Service**: Exposição dos serviços via ClusterIP, NodePort ou LoadBalancer.
- **ConfigMap**: Armazena configurações como variáveis de ambiente.
- **PersistentVolume (PV) e PersistentVolumeClaim (PVC)**: Garante persistência dos dados da blockchain.
- **Horizontal Pod Autoscaler (HPA)**: Ajusta automaticamente a quantidade de pods com base na carga.
---
## **2⃣ Configuração do Deployment**
📌 **Exemplo de `deployment.yaml`**:
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: dejo-node
spec:
replicas: 3
selector:
matchLabels:
app: dejo-node
template:
metadata:
labels:
app: dejo-node
spec:
containers:
- name: dejo-node
image: dejo/node:latest
ports:
- containerPort: 8545 # Porta RPC
- containerPort: 30303 # Porta P2P
envFrom:
- configMapRef:
name: dejo-config
volumeMounts:
- name: blockchain-storage
mountPath: /data
volumes:
- name: blockchain-storage
persistentVolumeClaim:
claimName: dejo-pvc
```
---
## **3⃣ Exposição do Serviço**
📌 **Exemplo de `service.yaml`**:
```yaml
apiVersion: v1
kind: Service
metadata:
name: dejo-service
spec:
type: LoadBalancer
ports:
- port: 8545
targetPort: 8545
protocol: TCP
name: rpc
- port: 30303
targetPort: 30303
protocol: TCP
name: p2p
selector:
app: dejo-node
```
---
## **4⃣ Escalabilidade com HPA**
📌 **Exemplo de `hpa.yaml`**:
```yaml
apiVersion: autoscaling/v2beta2
kind: HorizontalPodAutoscaler
metadata:
name: dejo-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: dejo-node
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
```
---
## **5⃣ Monitoramento e Logging**
### **5.1 - Monitoramento com Prometheus & Grafana**
📌 **Exemplo de configuração do Prometheus**:
```yaml
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: dejo-monitor
spec:
selector:
matchLabels:
app: dejo-node
endpoints:
- port: metrics
interval: 10s
```
### **5.2 - Logging com Fluentd e Elastic Stack**
- Logs coletados com **Fluentd** e enviados para **Elasticsearch**.
- Dashboards criados no **Kibana** para análise em tempo real.
---
📌 **Este documento será atualizado conforme novas estratégias forem implementadas.**
🚀 **DEJO Node: Kubernetes otimizado para escalabilidade e resiliência!** 🔥

91
docs/logging.md Normal file
View File

@ -0,0 +1,91 @@
# 📜 **Logging - DEJO Node**
## 📌 **Visão Geral**
O **DEJO Node** implementa um sistema de logging estruturado para **monitoramento, auditoria e depuração** do funcionamento da blockchain. Utilizamos **Zap (Uber-go)** para logs de alto desempenho e baixa latência.
---
## **1⃣ Níveis de Log**
Os logs seguem uma hierarquia de severidade:
- **DEBUG** → Informações detalhadas para desenvolvimento.
- **INFO** → Eventos operacionais normais.
- **WARN** → Eventos que podem exigir atenção.
- **ERROR** → Problemas que impactam a funcionalidade, mas não interrompem o serviço.
- **FATAL** → Falhas críticas que resultam no encerramento do serviço.
---
## **2⃣ Exemplo de Configuração de Logging**
📌 **Trecho do `config.yaml`**:
```yaml
logging:
level: "info"
output: "stdout" # Pode ser "stdout", "file" ou "json"
filePath: "/var/log/dejo-node.log"
format: "json" # Pode ser "json" ou "console"
```
📌 **Trecho de código usando Zap:**
```go
package logger
import (
"go.uber.org/zap"
)
var log *zap.Logger
func InitLogger(level string) {
var err error
cfg := zap.NewProductionConfig()
cfg.Level = zap.NewAtomicLevelAt(parseLogLevel(level))
log, err = cfg.Build()
if err != nil {
panic("Erro ao inicializar logger: " + err.Error())
}
}
func Info(msg string, fields ...zap.Field) {
log.Info(msg, fields...)
}
func Error(msg string, fields ...zap.Field) {
log.Error(msg, fields...)
}
func Fatal(msg string, fields ...zap.Field) {
log.Fatal(msg, fields...)
}
```
---
## **3⃣ Exemplo de Saída de Log**
📌 **Formato JSON:**
```json
{
"level": "info",
"timestamp": "2025-03-18T12:00:00Z",
"message": "Novo bloco minerado",
"blockHeight": 10234,
"hash": "0xa7b9c..."
}
```
📌 **Formato Console:**
```
[INFO] 2025-03-18T12:00:00Z Novo bloco minerado | blockHeight=10234 hash=0xa7b9c...
```
---
## **4⃣ Integração com Monitoramento**
- Logs podem ser enviados para **Elastic Stack (Elasticsearch + Kibana)**.
- Suporte a **Fluentd** para roteamento de logs para múltiplos destinos.
- **Prometheus e Grafana** podem coletar métricas baseadas nos logs.
---
📌 **Este documento será atualizado conforme novas funcionalidades forem adicionadas.**
🚀 **DEJO Node: Logging estruturado para máxima observabilidade!** 🔥

90
docs/openapi.yaml Normal file
View File

@ -0,0 +1,90 @@
openapi: 3.0.3
info:
title: DEJO Node API
description: API REST pública do nó da blockchain DEJO
version: 1.0.0
paths:
/health:
get:
summary: Verifica se o serviço está vivo
responses:
'200':
description: OK
/startup:
get:
summary: Verifica se o serviço foi inicializado
responses:
'200':
description: Serviço iniciado
'503':
description: Inicialização incompleta
/ready:
get:
summary: Verifica se o nó está pronto para receber requisições
responses:
'200':
description: Pronto para uso
'503':
description: Store ou Mempool indisponível
/tx:
post:
summary: Envia uma nova transação para a rede
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
from:
type: string
to:
type: string
value:
type: number
nonce:
type: integer
gas:
type: integer
signature:
type: string
responses:
'200':
description: Transação enviada
/block/{hash}:
get:
summary: Retorna um bloco pelo hash
parameters:
- name: hash
in: path
required: true
schema:
type: string
responses:
'200':
description: Dados do bloco
/tx/{hash}:
get:
summary: Retorna uma transação pelo hash
parameters:
- name: hash
in: path
required: true
schema:
type: string
responses:
'200':
description: Dados da transação
/mempool:
get:
summary: Retorna as transações pendentes
responses:
'200':
description: Lista de transações pendentes

92
docs/planning.md Normal file
View File

@ -0,0 +1,92 @@
# 🏗️ **Plano de Desenvolvimento - DEJO Node**
## 📌 **Visão Geral**
Este documento define o planejamento das tarefas para o desenvolvimento do **DEJO Node**, garantindo um roadmap estruturado para a implementação dos componentes essenciais.
---
## **1⃣ Criação da Estrutura do Projeto**
- **1.1 - Definir arquitetura de diretórios e módulos** ✅ **[OK]**
- **1.2 - Criar estrutura base do projeto (pastas e arquivos principais)** ✅ **[OK]**
- **1.3 - Configurar Makefile para build, testes e deploy** ✅ **[OK]**
- **1.4 - Criar Dockerfile otimizado para execução do nó** ✅ **[OK]**
- **1.5 - Configurar variáveis de ambiente e arquivos YAML de configuração** ✅ **[OK]**
- **1.6 - Criar script de inicialização do nó (`cmd/main.go`)** ✅ **[OK]**
---
## **2⃣ Desenvolvimento do Processamento de Transações**
🔹 **Dependência:** Nenhuma (autocontido, pode ser feito primeiro)
- **2.1 - Criar estrutura de transação (remetente, destinatário, assinatura, gas, etc.)** ✅
- **2.2 - Implementar validação de transações** ✅
- **2.3 - Criar sistema de Mempool para transações pendentes** ✅
- **2.4 - Criar mecanismo de inclusão de transações nos blocos** ✅
---
## **3⃣ Armazenamento e Persistência de Dados**
🔹 **Dependência:** Processamento de Transações finalizado
- **3.1 - Definir banco de dados e estrutura de armazenamento (LevelDB/BadgerDB)** ✅
- **3.2 - Criar estrutura de indexação de blocos e transações** ✅
- **3.3 - Implementar recuperação rápida de estados da blockchain** ✅
---
## **4⃣ Comunicação P2P entre Nós da Rede**
🔹 **Dependência:** Armazenamento e Transações finalizados
- **4.1 - Definir protocolo de comunicação (libp2p, WebSockets)** ✅
- **4.2 - Implementar descoberta de peers e inicialização da rede** ✅
- **4.3 - Criar sistema de sincronização de blocos entre nós** ✅
- **4.3.1 - Criar estrutura do módulo SyncManager** ✅
- **4.3.2 - Implementar troca de altura (RequestHeight / ResponseHeight)** ✅
- **4.3.3 - Implementar solicitação de blocos ausentes via stream** ✅
- **4.3.4 - Implementar envio de blocos em resposta a solicitações** ✅
- **4.3.5 - Validar blocos recebidos e aplicar no storage local** ✅
- **4.3.6 - Testes automatizados de sincronização entre dois nós** ✅
---
## **5⃣ Implementação do Mecanismo de Consenso**
🔹 **Dependência:** Comunicação P2P pronta
- **5.1 - Definir e implementar o algoritmo de consenso (PoS ou BFT)** ✅
- **5.2 - Criar mecanismo de finalização de blocos**
- **5.3 - Implementar auditoria e métricas para análise de consenso**
---
## **6⃣ Desenvolvimento da API RPC/WebSockets**
🔹 **Dependência:** Processamento de Transações e Consenso implementados
- **6.1 - Criar endpoints para consulta de blocos e transações**
- **6.2 - Implementar envio de novas transações via RPC**
- **6.3 - Criar suporte para eventos via WebSockets**
- **6.4 - Adicionar estrutura de resposta JSON detalhada na documentação**
---
## **7⃣ Implementação da Governança e Staking**
🔹 **Dependência:** Consenso implementado
- **7.1 - Criar sistema de staking para eleição de validadores**
- **7.2 - Implementar contratos de votação on-chain para governança**
- **7.3 - Criar API para registro e consulta de propostas de governança**
---
## **8⃣ Implementação dos Oráculos e Integração com Dados Externos**
🔹 **Dependência:** API RPC/WebSockets implementada
- **8.1 - Criar integração com provedores de dados externos (ex.: Chainlink, API3)**
- **8.2 - Implementar mecanismo de consenso para validação de dados oraculares**
- **8.3 - Criar sistema de auditoria para dados recebidos de oráculos**
---
## **9⃣ Sistema de Segurança e Proteção contra Ataques**
🔹 **Dependência:** Consenso implementado
- **9.1 - Implementar proteção contra ataques Sybil e DDoS**
- **9.2 - Criar auditoria e monitoramento de atividades suspeitas**
- **9.3 - Implementar criptografia pós-quântica para segurança futura**
---
📌 **Este documento será atualizado conforme a evolução do projeto.**
🚀 **DEJO Node: Rumo à construção de uma blockchain robusta e escalável!** 🔥

182
docs/roadmap_v1.0.0.md Normal file
View File

@ -0,0 +1,182 @@
## 🧩 Roadmap DEJO Node v1.0.0 — Documentação Técnica Operacional
---
### 🧠 1. Execução de Transações no Bloco
#### **1.1. Estrutura de Contas**
> Implementar estrutura de dados que armazena contas, com saldo e nonce.
- Criar uma struct `Account`:
```go
type Account struct {
Balance uint64
Nonce uint64
}
```
- Criar um `map[string]*Account` que representa o conjunto de contas (`state.accounts`)
- O acesso será via endereço (`string`) como chave
- Contas devem ser inicializadas com `Balance = 0` caso ainda não existam
---
#### **1.2. Aplicar Transações**
> Criar lógica que modifica o estado de contas ao aplicar uma transação do tipo `Transfer`.
- Criar função:
```go
func (s *State) ApplyTransaction(tx *Transaction) error
```
- Verificar se:
- Conta `from` existe
- `from.Balance >= tx.Value`
- `tx.Nonce == from.Nonce`
- Aplicar mudanças:
- Deduzir `Value` da conta `from`
- Acrescentar `Value` à conta `to`
- Incrementar `Nonce` da conta `from`
- Retornar erro se transação for inválida
---
#### **1.3. Executar lote de transações ao final do bloco**
> Aplicar todas as transações do bloco no commit.
- No final do `StartConsensusLoop`, quando o bloco é finalizado:
```go
for _, tx := range block.Txns {
err := state.ApplyTransaction(tx)
if err != nil {
log.Printf("❌ Erro ao aplicar tx: %+v\n", err)
}
}
```
---
### 📚 2. Módulo de Estado (`internal/state`)
#### **2.1. Estrutura do módulo `state`**
> Centralizar toda lógica de mutação de estado (contas, staking, dao).
- Criar `internal/state/state.go`
- Definir `State`:
```go
type State struct {
Accounts map[string]*Account
Staking *staking.StakingStore
DAO *dao.DAOStore
}
```
- Implementar métodos:
- `ApplyTransaction(tx *Transaction) error`
- `GetBalance(address string) uint64`
- `Mint(address string, value uint64)`
- `SaveToDisk(path string)` / `LoadFromDisk(path string)`
---
#### **2.2. Encapsular staking/DAO dentro de State**
> Injetar dependência de staking e DAO no state para simplificar o acesso global.
- Criar `NewState()` que instancia os campos `Accounts`, `Staking` e `DAO`
- Atualizar consenso e handlers para usar `state.Staking` e `state.DAO` em vez de referências diretas
---
### 💸 3. Recompensas e Penalidades
#### **3.1. Recompensa**
> Premiar validadores que participaram corretamente da rodada.
- No commit do bloco:
- Calcular stake total que participou
- Distribuir `totalReward = 5 tokens` proporcionalmente:
```go
reward := (validatorStake * totalReward) / totalStake
state.Mint(validatorID, reward)
```
- Validadores que não participaram não recebem nada
---
#### **3.2. Slash**
> Penalizar validadores que não participaram da rodada.
- Após o commit:
- Identificar validadores ativos que **não** fizeram `PRECOMMIT`
- Deduzir 50 tokens via:
```go
state.accounts[validator].Balance -= 50
```
---
### 🌐 4. API REST Estendida
#### **4.1. `/accounts/{addr}`**
> Obter saldo e informações de conta.
- GET `/accounts/{addr}`
- Retornar:
```json
{
"address": "node-A",
"balance": 950,
"nonce": 3
}
```
---
#### **4.2. `/validators`**
> Obter todos validadores ativos.
- GET `/validators`
- Retornar lista:
```json
[
{ "address": "node-A", "stake": 1000, "status": "online" },
...
]
```
---
#### **4.3. `/dao/status`**
> Visualizar estado atual da DAO.
- GET `/dao/status`
- Retornar:
- Propostas em aberto
- Número de votos
- % de quorum atingido
- Tempo restante
---
### 🧪 5. Testes
#### **5.1. Testes com múltiplos nós**
> Verificar se transações em node-A aparecem em blocos de node-B.
- Lançar `node-A` e `node-B`
- Enviar `POST /transaction` em A
- Verificar `GET /blocks` em B e encontrar a transação
---
#### **5.2. Validação de saldo**
> Testar aplicação de transações.
- Verificar que saldo de `from` diminui
- Verificar que saldo de `to` aumenta
- Verificar que `Nonce` incrementa
---
#### **5.3. Testes de recompensas e penalidades**
> Validar que rewards/slash funcionam corretamente.
- Propor bloco com `node-A`, verificar +5 tokens
- Omissão de `PRECOMMIT` de `node-B`, verificar -50 tokens

56
docs/tasks_todo.md Normal file
View File

@ -0,0 +1,56 @@
# ✅ Checklist Detalhado de Tarefas Pendentes - DEJO Node
Este arquivo será utilizado para acompanhar o progresso detalhado das pendências restantes até a finalização da primeira versão completa do `dejo_node`.
---
## 🧪 Revisão e Correção de Funcionalidades Existentes
- [ ] Verificar se todos os pacotes possuem `go build` e `go test` funcionando
- [ ] Consolidar estrutura de `Block` em `blockchain/block.go`
- [ ] Validar `finality.go` (consensus/simple)
- [ ] Verificar uso correto de `storage.Storage`
- [ ] Confirmar estrutura correta do bloco (sem campos desconhecidos)
---
## 🔧 Healthcheck
- [ ] Criar pacote `internal/healthcheck`
- [ ] Implementar endpoint `/live`
- [ ] Implementar endpoint `/ready`
- [ ] Implementar endpoint `/startup`
- [ ] Incluir integração com `main.go`
---
## 🧠 Mempool
- [ ] Validar se `mempool` realmente existe ou precisa ser recriado
- [ ] Se necessário, recriar:
- [ ] Estrutura de pool de transações pendentes
- [ ] Métodos para adicionar, validar e expurgar transações
---
## 🔌 RPC/WebSockets
- [ ] Criar pacote `internal/rpc`
- [ ] Endpoint para consulta de bloco por hash
- [ ] Endpoint para envio de nova transação
- [ ] Suporte WebSocket para eventos em tempo real
---
## 🔐 Security
- [ ] Definir escopo inicial de `security`
- [ ] Criar proteção básica contra flood (ex.: IP rate limit)
- [ ] Adicionar logs de requisições suspeitas
---
## 📄 Documentação e Planejamento
- [ ] Atualizar `docs/planning.md` com estado real das fases
- [ ] Atualizar `/docs/tasks_todo.md` a cada entrega

139
go.mod Normal file
View File

@ -0,0 +1,139 @@
module dejo_node
go 1.23.0
toolchain go1.23.2
require (
github.com/gorilla/mux v1.8.1
github.com/gorilla/websocket v1.5.3
github.com/libp2p/go-libp2p v0.41.1
github.com/libp2p/go-libp2p-kad-dht v0.30.2
github.com/multiformats/go-multiaddr v0.15.0
github.com/prometheus/client_golang v1.21.1
github.com/stretchr/testify v1.10.0
github.com/syndtr/goleveldb v1.0.0
github.com/ulule/limiter/v3 v3.11.2
go.uber.org/zap v1.27.0
)
require (
github.com/benbjohnson/clock v1.3.5 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/containerd/cgroups v1.1.0 // indirect
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/elastic/gosigar v0.14.3 // indirect
github.com/flynn/noise v1.1.0 // indirect
github.com/francoispqt/gojay v1.2.13 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db // indirect
github.com/google/gopacket v1.1.19 // indirect
github.com/google/pprof v0.0.0-20250208200701-d0013a598941 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/hashicorp/golang-lru v1.0.2 // indirect
github.com/huin/goupnp v1.3.0 // indirect
github.com/ipfs/boxo v0.28.0 // indirect
github.com/ipfs/go-cid v0.5.0 // indirect
github.com/ipfs/go-datastore v0.8.2 // indirect
github.com/ipfs/go-log v1.0.5 // indirect
github.com/ipfs/go-log/v2 v2.5.1 // indirect
github.com/ipld/go-ipld-prime v0.21.0 // indirect
github.com/jackpal/go-nat-pmp v1.0.2 // indirect
github.com/jbenet/go-temp-err-catcher v0.1.0 // indirect
github.com/klauspost/compress v1.18.0 // indirect
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
github.com/koron/go-ssdp v0.0.5 // indirect
github.com/libp2p/go-buffer-pool v0.1.0 // indirect
github.com/libp2p/go-cidranger v1.1.0 // indirect
github.com/libp2p/go-flow-metrics v0.2.0 // indirect
github.com/libp2p/go-libp2p-asn-util v0.4.1 // indirect
github.com/libp2p/go-libp2p-kbucket v0.6.5 // indirect
github.com/libp2p/go-libp2p-record v0.3.1 // indirect
github.com/libp2p/go-libp2p-routing-helpers v0.7.5 // indirect
github.com/libp2p/go-msgio v0.3.0 // indirect
github.com/libp2p/go-netroute v0.2.2 // indirect
github.com/libp2p/go-reuseport v0.4.0 // indirect
github.com/libp2p/go-yamux/v5 v5.0.0 // indirect
github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/miekg/dns v1.1.63 // indirect
github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b // indirect
github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect
github.com/minio/sha256-simd v1.0.1 // indirect
github.com/mr-tron/base58 v1.2.0 // indirect
github.com/multiformats/go-base32 v0.1.0 // indirect
github.com/multiformats/go-base36 v0.2.0 // indirect
github.com/multiformats/go-multiaddr-dns v0.4.1 // indirect
github.com/multiformats/go-multiaddr-fmt v0.1.0 // indirect
github.com/multiformats/go-multibase v0.2.0 // indirect
github.com/multiformats/go-multicodec v0.9.0 // indirect
github.com/multiformats/go-multihash v0.2.3 // indirect
github.com/multiformats/go-multistream v0.6.0 // indirect
github.com/multiformats/go-varint v0.0.7 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/onsi/ginkgo/v2 v2.22.2 // indirect
github.com/opencontainers/runtime-spec v1.2.0 // indirect
github.com/opentracing/opentracing-go v1.2.0 // indirect
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect
github.com/pion/datachannel v1.5.10 // indirect
github.com/pion/dtls/v2 v2.2.12 // indirect
github.com/pion/dtls/v3 v3.0.4 // indirect
github.com/pion/ice/v4 v4.0.8 // indirect
github.com/pion/interceptor v0.1.37 // indirect
github.com/pion/logging v0.2.3 // indirect
github.com/pion/mdns/v2 v2.0.7 // indirect
github.com/pion/randutil v0.1.0 // indirect
github.com/pion/rtcp v1.2.15 // indirect
github.com/pion/rtp v1.8.11 // indirect
github.com/pion/sctp v1.8.37 // indirect
github.com/pion/sdp/v3 v3.0.10 // indirect
github.com/pion/srtp/v3 v3.0.4 // indirect
github.com/pion/stun v0.6.1 // indirect
github.com/pion/stun/v3 v3.0.0 // indirect
github.com/pion/transport/v2 v2.2.10 // indirect
github.com/pion/transport/v3 v3.0.7 // indirect
github.com/pion/turn/v4 v4.0.0 // indirect
github.com/pion/webrtc/v4 v4.0.10 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/polydawn/refmt v0.89.0 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.62.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/quic-go/qpack v0.5.1 // indirect
github.com/quic-go/quic-go v0.50.1 // indirect
github.com/quic-go/webtransport-go v0.8.1-0.20241018022711-4ac2c9250e66 // indirect
github.com/raulk/go-watchdog v1.3.0 // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect
github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 // indirect
github.com/wlynxg/anet v0.0.5 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/otel v1.34.0 // indirect
go.opentelemetry.io/otel/metric v1.34.0 // indirect
go.opentelemetry.io/otel/trace v1.34.0 // indirect
go.uber.org/dig v1.18.0 // indirect
go.uber.org/fx v1.23.0 // indirect
go.uber.org/mock v0.5.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/crypto v0.35.0 // indirect
golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa // indirect
golang.org/x/mod v0.23.0 // indirect
golang.org/x/net v0.35.0 // indirect
golang.org/x/sync v0.11.0 // indirect
golang.org/x/sys v0.30.0 // indirect
golang.org/x/text v0.22.0 // indirect
golang.org/x/tools v0.30.0 // indirect
gonum.org/v1/gonum v0.15.1 // indirect
google.golang.org/protobuf v1.36.5 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
lukechampine.com/blake3 v1.4.0 // indirect
)

628
go.sum Normal file
View File

@ -0,0 +1,628 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo=
dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU=
dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU=
dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o=
github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/containerd/cgroups v0.0.0-20201119153540-4cbc285b3327/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE=
github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM=
github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw=
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c h1:pFUpOrbxDR6AkioZ1ySsx5yxlDQZ8stG2b88gTPxgJU=
github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c/go.mod h1:6UhI8N9EjYm1c2odKpFpAYeR8dsBeM7PtzQhRgxRr9U=
github.com/decred/dcrd/crypto/blake256 v1.1.0 h1:zPMNGQCm0g4QTY27fOCorQW7EryeQ/U0x++OzVrdms8=
github.com/decred/dcrd/crypto/blake256 v1.1.0/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40=
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/elastic/gosigar v0.12.0/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs=
github.com/elastic/gosigar v0.14.3 h1:xwkKwPia+hSfg9GqrCUKYdId102m9qTJIIr7egmK/uo=
github.com/elastic/gosigar v0.14.3/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/flynn/noise v1.1.0 h1:KjPQoQCEFdZDiP03phOvGi11+SVVhBG2wOWAorLsstg=
github.com/flynn/noise v1.1.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag=
github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=
github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0=
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20250208200701-d0013a598941 h1:43XjGa6toxLpeksjcxs1jIoIyr+vUfOqY2c6HB4bpoc=
github.com/google/pprof v0.0.0-20250208200701-d0013a598941/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c h1:7lF+Vz0LqiRidnzC1Oq86fpX1q/iEv2KJdrCtttYjT4=
github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c=
github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc=
github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8=
github.com/ipfs/boxo v0.28.0 h1:io6nXqN8XMOstB7dQGG5GWnMk4WssoMvva9OADErZdI=
github.com/ipfs/boxo v0.28.0/go.mod h1:eY9w3iTpmZGKzDfEYjm3oK8f+xjv8KJhhNXJwicmd3I=
github.com/ipfs/go-block-format v0.2.0 h1:ZqrkxBA2ICbDRbK8KJs/u0O3dlp6gmAuuXUJNiW1Ycs=
github.com/ipfs/go-block-format v0.2.0/go.mod h1:+jpL11nFx5A/SPpsoBn6Bzkra/zaArfSmsknbPMYgzM=
github.com/ipfs/go-cid v0.5.0 h1:goEKKhaGm0ul11IHA7I6p1GmKz8kEYniqFopaB5Otwg=
github.com/ipfs/go-cid v0.5.0/go.mod h1:0L7vmeNXpQpUS9vt+yEARkJ8rOg43DF3iPgn4GIN0mk=
github.com/ipfs/go-datastore v0.8.2 h1:Jy3wjqQR6sg/LhyY0NIePZC3Vux19nLtg7dx0TVqr6U=
github.com/ipfs/go-datastore v0.8.2/go.mod h1:W+pI1NsUsz3tcsAACMtfC+IZdnQTnC/7VfPoJBQuts0=
github.com/ipfs/go-detect-race v0.0.1 h1:qX/xay2W3E4Q1U7d9lNs1sU9nvguX0a7319XbyQ6cOk=
github.com/ipfs/go-detect-race v0.0.1/go.mod h1:8BNT7shDZPo99Q74BpGMK+4D8Mn4j46UU0LZ723meps=
github.com/ipfs/go-ipfs-util v0.0.3 h1:2RFdGez6bu2ZlZdI+rWfIdbQb1KudQp3VGwPtdNCmE0=
github.com/ipfs/go-ipfs-util v0.0.3/go.mod h1:LHzG1a0Ig4G+iZ26UUOMjHd+lfM84LZCrn17xAKWBvs=
github.com/ipfs/go-log v1.0.5 h1:2dOuUCB1Z7uoczMWgAyDck5JLb72zHzrMnGnCNNbvY8=
github.com/ipfs/go-log v1.0.5/go.mod h1:j0b8ZoR+7+R99LD9jZ6+AJsrzkPbSXbZfGakb5JPtIo=
github.com/ipfs/go-log/v2 v2.1.3/go.mod h1:/8d0SH3Su5Ooc31QlL1WysJhvyOTDCjcCZ9Axpmri6g=
github.com/ipfs/go-log/v2 v2.5.1 h1:1XdUzF7048prq4aBjDQQ4SL5RxftpRGdXhNRwKSAlcY=
github.com/ipfs/go-log/v2 v2.5.1/go.mod h1:prSpmC1Gpllc9UYWxDiZDreBYw7zp4Iqp1kOLU9U5UI=
github.com/ipfs/go-test v0.2.1 h1:/D/a8xZ2JzkYqcVcV/7HYlCnc7bv/pKHQiX5TdClkPE=
github.com/ipfs/go-test v0.2.1/go.mod h1:dzu+KB9cmWjuJnXFDYJwC25T3j1GcN57byN+ixmK39M=
github.com/ipld/go-ipld-prime v0.21.0 h1:n4JmcpOlPDIxBcY037SVfpd1G+Sj1nKZah0m6QH9C2E=
github.com/ipld/go-ipld-prime v0.21.0/go.mod h1:3RLqy//ERg/y5oShXXdx5YIp50cFGOanyMctpPjsvxQ=
github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus=
github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=
github.com/jbenet/go-temp-err-catcher v0.1.0 h1:zpb3ZH6wIE8Shj2sKS+khgRvf7T7RABoLk/+KKHggpk=
github.com/jbenet/go-temp-err-catcher v0.1.0/go.mod h1:0kJRvmDZXNMIiJirNPEYfhpPwbGVtZVWC34vc5WLsDk=
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/koron/go-ssdp v0.0.5 h1:E1iSMxIs4WqxTbIBLtmNBeOOC+1sCIXQeqTWVnpmwhk=
github.com/koron/go-ssdp v0.0.5/go.mod h1:Qm59B7hpKpDqfyRNWRNr00jGwLdXjDyZh6y7rH6VS0w=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8=
github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg=
github.com/libp2p/go-cidranger v1.1.0 h1:ewPN8EZ0dd1LSnrtuwd4709PXVcITVeuwbag38yPW7c=
github.com/libp2p/go-cidranger v1.1.0/go.mod h1:KWZTfSr+r9qEo9OkI9/SIEeAtw+NNoU0dXIXt15Okic=
github.com/libp2p/go-flow-metrics v0.2.0 h1:EIZzjmeOE6c8Dav0sNv35vhZxATIXWZg6j/C08XmmDw=
github.com/libp2p/go-flow-metrics v0.2.0/go.mod h1:st3qqfu8+pMfh+9Mzqb2GTiwrAGjIPszEjZmtksN8Jc=
github.com/libp2p/go-libp2p v0.41.1 h1:8ecNQVT5ev/jqALTvisSJeVNvXYJyK4NhQx1nNRXQZE=
github.com/libp2p/go-libp2p v0.41.1/go.mod h1:DcGTovJzQl/I7HMrby5ZRjeD0kQkGiy+9w6aEkSZpRI=
github.com/libp2p/go-libp2p-asn-util v0.4.1 h1:xqL7++IKD9TBFMgnLPZR6/6iYhawHKHl950SO9L6n94=
github.com/libp2p/go-libp2p-asn-util v0.4.1/go.mod h1:d/NI6XZ9qxw67b4e+NgpQexCIiFYJjErASrYW4PFDN8=
github.com/libp2p/go-libp2p-kad-dht v0.30.2 h1:K0LJPdXynQ+u3rx6uFlrfNy0i11LE6SOCDzwAAaahys=
github.com/libp2p/go-libp2p-kad-dht v0.30.2/go.mod h1:UV0mxF4ufh/ht05jNg5mcjOMrjK82uecgANa+GKi4y0=
github.com/libp2p/go-libp2p-kbucket v0.6.5 h1:Fsl1YvZcMwqrR4DYrTO02yo9PGYs2HBQIT3lGXFMTxg=
github.com/libp2p/go-libp2p-kbucket v0.6.5/go.mod h1:U6WOd0BvnSp03IQSrjgM54tg7zh1UUNsXLJqAQzClTA=
github.com/libp2p/go-libp2p-record v0.3.1 h1:cly48Xi5GjNw5Wq+7gmjfBiG9HCzQVkiZOUZ8kUl+Fg=
github.com/libp2p/go-libp2p-record v0.3.1/go.mod h1:T8itUkLcWQLCYMqtX7Th6r7SexyUJpIyPgks757td/E=
github.com/libp2p/go-libp2p-routing-helpers v0.7.5 h1:HdwZj9NKovMx0vqq6YNPTh6aaNzey5zHD7HeLJtq6fI=
github.com/libp2p/go-libp2p-routing-helpers v0.7.5/go.mod h1:3YaxrwP0OBPDD7my3D0KxfR89FlcX/IEbxDEDfAmj98=
github.com/libp2p/go-libp2p-testing v0.12.0 h1:EPvBb4kKMWO29qP4mZGyhVzUyR25dvfUIK5WDu6iPUA=
github.com/libp2p/go-libp2p-testing v0.12.0/go.mod h1:KcGDRXyN7sQCllucn1cOOS+Dmm7ujhfEyXQL5lvkcPg=
github.com/libp2p/go-msgio v0.3.0 h1:mf3Z8B1xcFN314sWX+2vOTShIE0Mmn2TXn3YCUQGNj0=
github.com/libp2p/go-msgio v0.3.0/go.mod h1:nyRM819GmVaF9LX3l03RMh10QdOroF++NBbxAb0mmDM=
github.com/libp2p/go-netroute v0.2.2 h1:Dejd8cQ47Qx2kRABg6lPwknU7+nBnFRpko45/fFPuZ8=
github.com/libp2p/go-netroute v0.2.2/go.mod h1:Rntq6jUAH0l9Gg17w5bFGhcC9a+vk4KNXs6s7IljKYE=
github.com/libp2p/go-reuseport v0.4.0 h1:nR5KU7hD0WxXCJbmw7r2rhRYruNRl2koHw8fQscQm2s=
github.com/libp2p/go-reuseport v0.4.0/go.mod h1:ZtI03j/wO5hZVDFo2jKywN6bYKWLOy8Se6DrI2E1cLU=
github.com/libp2p/go-yamux/v5 v5.0.0 h1:2djUh96d3Jiac/JpGkKs4TO49YhsfLopAoryfPmf+Po=
github.com/libp2p/go-yamux/v5 v5.0.0/go.mod h1:en+3cdX51U0ZslwRdRLrvQsdayFt3TSUKvBGErzpWbU=
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd h1:br0buuQ854V8u83wA0rVZ8ttrq5CpaPZdvrK0LP2lOk=
github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd/go.mod h1:QuCEs1Nt24+FYQEqAAncTDPJIuGs+LxK1MCiFL25pMU=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY=
github.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs=
github.com/mikioh/tcp v0.0.0-20190314235350-803a9b46060c h1:bzE/A84HN25pxAuk9Eej1Kz9OUelF97nAc82bDquQI8=
github.com/mikioh/tcp v0.0.0-20190314235350-803a9b46060c/go.mod h1:0SQS9kMwD2VsyFEB++InYyBJroV/FRmBgcydeSUcJms=
github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b h1:z78hV3sbSMAUoyUMM0I83AUIT6Hu17AWfgjzIbtrYFc=
github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b/go.mod h1:lxPUiZwKoFL8DUUmalo2yJJUCxbPKtm8OKfqr2/FTNU=
github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc h1:PTfri+PuQmWDqERdnNMiD9ZejrlswWrCpBEZgWOiTrc=
github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc/go.mod h1:cGKTAVKx4SxOuR/czcZ/E2RSJ3sfHs8FpHhQ5CWMf9s=
github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ=
github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM=
github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mr-tron/base58 v1.1.2/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE=
github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI=
github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0=
github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4=
github.com/multiformats/go-multiaddr v0.1.1/go.mod h1:aMKBKNEYmzmDmxfX88/vz+J5IU55txyt0p4aiWVohjo=
github.com/multiformats/go-multiaddr v0.15.0 h1:zB/HeaI/apcZiTDwhY5YqMvNVl/oQYvs3XySU+qeAVo=
github.com/multiformats/go-multiaddr v0.15.0/go.mod h1:JSVUmXDjsVFiW7RjIFMP7+Ev+h1DTbiJgVeTV/tcmP0=
github.com/multiformats/go-multiaddr-dns v0.4.1 h1:whi/uCLbDS3mSEUMb1MsoT4uzUeZB0N32yzufqS0i5M=
github.com/multiformats/go-multiaddr-dns v0.4.1/go.mod h1:7hfthtB4E4pQwirrz+J0CcDUfbWzTqEzVyYKKIKpgkc=
github.com/multiformats/go-multiaddr-fmt v0.1.0 h1:WLEFClPycPkp4fnIzoFoV9FVd49/eQsuaL3/CWe167E=
github.com/multiformats/go-multiaddr-fmt v0.1.0/go.mod h1:hGtDIW4PU4BqJ50gW2quDuPVjyWNZxToGUh/HwTZYJo=
github.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g=
github.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk=
github.com/multiformats/go-multicodec v0.9.0 h1:pb/dlPnzee/Sxv/j4PmkDRxCOi3hXTz3IbPKOXWJkmg=
github.com/multiformats/go-multicodec v0.9.0/go.mod h1:L3QTQvMIaVBkXOXXtVmYE+LI16i14xuaojr/H7Ai54k=
github.com/multiformats/go-multihash v0.0.8/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew=
github.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7BFvVU9RSh+U=
github.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM=
github.com/multiformats/go-multistream v0.6.0 h1:ZaHKbsL404720283o4c/IHQXiS6gb8qAN5EIJ4PN5EA=
github.com/multiformats/go-multistream v0.6.0/go.mod h1:MOyoG5otO24cHIg8kf9QW2/NozURlkP/rvi2FQJyCPg=
github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8=
github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY=
github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/ginkgo/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU=
github.com/onsi/ginkgo/v2 v2.22.2/go.mod h1:oeMosUL+8LtarXBHu/c0bx2D/K9zyQ6uX3cTyztHwsk=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8=
github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY=
github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/opencontainers/runtime-spec v1.2.0 h1:z97+pHb3uELt/yiAWD691HNHQIF07bE7dzrbT927iTk=
github.com/opencontainers/runtime-spec v1.2.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0=
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y=
github.com/pion/datachannel v1.5.10 h1:ly0Q26K1i6ZkGf42W7D4hQYR90pZwzFOjTq5AuCKk4o=
github.com/pion/datachannel v1.5.10/go.mod h1:p/jJfC9arb29W7WrxyKbepTU20CFgyx5oLo8Rs4Py/M=
github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s=
github.com/pion/dtls/v2 v2.2.12 h1:KP7H5/c1EiVAAKUmXyCzPiQe5+bCJrpOeKg/L05dunk=
github.com/pion/dtls/v2 v2.2.12/go.mod h1:d9SYc9fch0CqK90mRk1dC7AkzzpwJj6u2GU3u+9pqFE=
github.com/pion/dtls/v3 v3.0.4 h1:44CZekewMzfrn9pmGrj5BNnTMDCFwr+6sLH+cCuLM7U=
github.com/pion/dtls/v3 v3.0.4/go.mod h1:R373CsjxWqNPf6MEkfdy3aSe9niZvL/JaKlGeFphtMg=
github.com/pion/ice/v4 v4.0.8 h1:ajNx0idNG+S+v9Phu4LSn2cs8JEfTsA1/tEjkkAVpFY=
github.com/pion/ice/v4 v4.0.8/go.mod h1:y3M18aPhIxLlcO/4dn9X8LzLLSma84cx6emMSu14FGw=
github.com/pion/interceptor v0.1.37 h1:aRA8Zpab/wE7/c0O3fh1PqY0AJI3fCSEM5lRWJVorwI=
github.com/pion/interceptor v0.1.37/go.mod h1:JzxbJ4umVTlZAf+/utHzNesY8tmRkM2lVmkS82TTj8Y=
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
github.com/pion/logging v0.2.3 h1:gHuf0zpoh1GW67Nr6Gj4cv5Z9ZscU7g/EaoC/Ke/igI=
github.com/pion/logging v0.2.3/go.mod h1:z8YfknkquMe1csOrxK5kc+5/ZPAzMxbKLX5aXpbpC90=
github.com/pion/mdns/v2 v2.0.7 h1:c9kM8ewCgjslaAmicYMFQIde2H9/lrZpjBkN8VwoVtM=
github.com/pion/mdns/v2 v2.0.7/go.mod h1:vAdSYNAT0Jy3Ru0zl2YiW3Rm/fJCwIeM0nToenfOJKA=
github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
github.com/pion/rtcp v1.2.15 h1:LZQi2JbdipLOj4eBjK4wlVoQWfrZbh3Q6eHtWtJBZBo=
github.com/pion/rtcp v1.2.15/go.mod h1:jlGuAjHMEXwMUHK78RgX0UmEJFV4zUKOFHR7OP+D3D0=
github.com/pion/rtp v1.8.11 h1:17xjnY5WO5hgO6SD3/NTIUPvSFw/PbLsIJyz1r1yNIk=
github.com/pion/rtp v1.8.11/go.mod h1:8uMBJj32Pa1wwx8Fuv/AsFhn8jsgw+3rUC2PfoBZ8p4=
github.com/pion/sctp v1.8.37 h1:ZDmGPtRPX9mKCiVXtMbTWybFw3z/hVKAZgU81wcOrqs=
github.com/pion/sctp v1.8.37/go.mod h1:cNiLdchXra8fHQwmIoqw0MbLLMs+f7uQ+dGMG2gWebE=
github.com/pion/sdp/v3 v3.0.10 h1:6MChLE/1xYB+CjumMw+gZ9ufp2DPApuVSnDT8t5MIgA=
github.com/pion/sdp/v3 v3.0.10/go.mod h1:88GMahN5xnScv1hIMTqLdu/cOcUkj6a9ytbncwMCq2E=
github.com/pion/srtp/v3 v3.0.4 h1:2Z6vDVxzrX3UHEgrUyIGM4rRouoC7v+NiF1IHtp9B5M=
github.com/pion/srtp/v3 v3.0.4/go.mod h1:1Jx3FwDoxpRaTh1oRV8A/6G1BnFL+QI82eK4ms8EEJQ=
github.com/pion/stun v0.6.1 h1:8lp6YejULeHBF8NmV8e2787BogQhduZugh5PdhDyyN4=
github.com/pion/stun v0.6.1/go.mod h1:/hO7APkX4hZKu/D0f2lHzNyvdkTGtIy3NDmLR7kSz/8=
github.com/pion/stun/v3 v3.0.0 h1:4h1gwhWLWuZWOJIJR9s2ferRO+W3zA/b6ijOI6mKzUw=
github.com/pion/stun/v3 v3.0.0/go.mod h1:HvCN8txt8mwi4FBvS3EmDghW6aQJ24T+y+1TKjB5jyU=
github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1Aq29pGcU5g=
github.com/pion/transport/v2 v2.2.4/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0=
github.com/pion/transport/v2 v2.2.10 h1:ucLBLE8nuxiHfvkFKnkDQRYWYfp8ejf4YBOPfaQpw6Q=
github.com/pion/transport/v2 v2.2.10/go.mod h1:sq1kSLWs+cHW9E+2fJP95QudkzbK7wscs8yYgQToO5E=
github.com/pion/transport/v3 v3.0.7 h1:iRbMH05BzSNwhILHoBoAPxoB9xQgOaJk+591KC9P1o0=
github.com/pion/transport/v3 v3.0.7/go.mod h1:YleKiTZ4vqNxVwh77Z0zytYi7rXHl7j6uPLGhhz9rwo=
github.com/pion/turn/v4 v4.0.0 h1:qxplo3Rxa9Yg1xXDxxH8xaqcyGUtbHYw4QSCvmFWvhM=
github.com/pion/turn/v4 v4.0.0/go.mod h1:MuPDkm15nYSklKpN8vWJ9W2M0PlyQZqYt1McGuxG7mA=
github.com/pion/webrtc/v4 v4.0.10 h1:Hq/JLjhqLxi+NmCtE8lnRPDr8H4LcNvwg8OxVcdv56Q=
github.com/pion/webrtc/v4 v4.0.10/go.mod h1:ViHLVaNpiuvaH8pdiuQxuA9awuE6KVzAXx3vVWilOck=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/polydawn/refmt v0.89.0 h1:ADJTApkvkeBZsN0tBTx8QjpD9JkmxbKp0cxfr9qszm4=
github.com/polydawn/refmt v0.89.0/go.mod h1:/zvteZs/GwLtCgZ4BL6CBsk9IKIlexP43ObX9AxTqTw=
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.21.1 h1:DOvXXTqVzvkIewV/CDPFdejpMCGeMcbGCQ8YOmu+Ibk=
github.com/prometheus/client_golang v1.21.1/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io=
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
github.com/quic-go/quic-go v0.50.1 h1:unsgjFIUqW8a2oopkY7YNONpV1gYND6Nt9hnt1PN94Q=
github.com/quic-go/quic-go v0.50.1/go.mod h1:Vim6OmUvlYdwBhXP9ZVrtGmCMWa3wEqhq3NgYrI8b4E=
github.com/quic-go/webtransport-go v0.8.1-0.20241018022711-4ac2c9250e66 h1:4WFk6u3sOT6pLa1kQ50ZVdm8BQFgJNA117cepZxtLIg=
github.com/quic-go/webtransport-go v0.8.1-0.20241018022711-4ac2c9250e66/go.mod h1:Vp72IJajgeOL6ddqrAhmp7IM9zbTcgkQxD/YdxrVwMw=
github.com/raulk/go-watchdog v1.3.0 h1:oUmdlHxdkXRJlwfG0O9omj8ukerm8MEQavSiDTEtBsk=
github.com/raulk/go-watchdog v1.3.0/go.mod h1:fIvOnLbF0b0ZwkB9YU4mOW9Did//4vPZtDqv66NfsMU=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=
github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM=
github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0=
github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw=
github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI=
github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU=
github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag=
github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg=
github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw=
github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y=
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q=
github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ=
github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I=
github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0=
github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ=
github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk=
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4=
github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs=
github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg=
github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3Pg9vgXWeJpQFMM=
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
github.com/ulule/limiter/v3 v3.11.2 h1:P4yOrxoEMJbOTfRJR2OzjL90oflzYPPmWg+dvwN2tHA=
github.com/ulule/limiter/v3 v3.11.2/go.mod h1:QG5GnFOCV+k7lrL5Y8kgEeeflPH3+Cviqlqa8SVSQxI=
github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0 h1:GDDkbFiaK8jsSDJfjId/PEGEShv6ugrt4kYsC5UIDaQ=
github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw=
github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 h1:EKhdznlJHPMoKr0XTrX+IlJs1LH3lyx2nfr1dOlZ79k=
github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1/go.mod h1:8UvriyWtv5Q5EOgjHaSseUEdkQfvwFv1I/In/O2M9gc=
github.com/wlynxg/anet v0.0.3/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA=
github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU=
github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ=
go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE=
go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k=
go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE=
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/dig v1.18.0 h1:imUL1UiY0Mg4bqbFfsRQO5G4CGRBec/ZujWTvSVp3pw=
go.uber.org/dig v1.18.0/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE=
go.uber.org/fx v1.23.0 h1:lIr/gYWQGfTwGcSXWXu4vP5Ws6iqnNEIY+F/aFzCKTg=
go.uber.org/fx v1.23.0/go.mod h1:o/D9n+2mLP6v1EG+qsdT1O8wKopYAsqZasju97SDFCU=
go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ=
go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200602180216-279210d13fed/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa h1:t2QcU6V556bFjYgu4L6C+6VrCPyJZ+eyRsABUPs1mz4=
golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa/go.mod h1:BHOTPb3L19zxehTsLoJXVaTktb06DFgmdW6Wb9s8jqk=
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM=
golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180810173357-98c5dad5d1a0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY=
golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gonum.org/v1/gonum v0.15.1 h1:FNy7N6OUZVUaWG9pTiD+jlhdQ3lMP+/LcTpJ6+a8sQ0=
gonum.org/v1/gonum v0.15.1/go.mod h1:eZTZuRFrzu5pcyjN5wJhcIhnUdNijYxX1T2IcrOGY0o=
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
lukechampine.com/blake3 v1.4.0 h1:xDbKOZCVbnZsfzM6mHSYcGRHZ3YrLDzqz8XnV4uaD5w=
lukechampine.com/blake3 v1.4.0/go.mod h1:MQJNQCTnR+kwOP/JEZSxj3MaQjp80FOFSNMMHXcSeX0=
sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=
sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=

View File

@ -0,0 +1,82 @@
package api
import (
"dejo_node/internal/state"
"dejo_node/internal/storage"
"encoding/json"
"net/http"
"strings"
)
var GlobalState *state.State
var GlobalBlockStore *storage.BlockStore
func HandleGetBalance(w http.ResponseWriter, r *http.Request) {
addr := strings.TrimPrefix(r.URL.Path, "/accounts/")
if strings.Contains(addr, "/") {
addr = strings.Split(addr, "/")[0]
}
if addr == "" {
http.Error(w, "endereço inválido", http.StatusBadRequest)
return
}
balance := GlobalState.GetBalance(addr)
resp := map[string]interface{}{
"address": addr,
"balance": balance,
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(resp)
}
func HandleGetTransactionsByAddress(w http.ResponseWriter, r *http.Request) {
addr := strings.TrimPrefix(r.URL.Path, "/accounts/")
addr = strings.TrimSuffix(addr, "/txs")
if addr == "" {
http.Error(w, "endereço inválido", http.StatusBadRequest)
return
}
txs := []map[string]interface{}{}
blocks, err := GlobalBlockStore.LoadAll()
if err != nil {
http.Error(w, "erro ao carregar blocos", http.StatusInternalServerError)
return
}
for _, blk := range blocks {
for _, tx := range blk.Txns {
if tx.From == addr || tx.To == addr {
txs = append(txs, map[string]interface{}{
"from": tx.From,
"to": tx.To,
"value": tx.Value,
"hash": tx.Hash(),
"block": blk.Index,
})
}
}
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(txs)
}
func (h *Handler) GetTransactionByHash(w http.ResponseWriter, r *http.Request) {
hash := strings.TrimPrefix(r.URL.Path, "/transaction/")
blocks, err := h.Store.LoadAll()
if err != nil {
http.Error(w, "erro ao carregar blocos", http.StatusInternalServerError)
return
}
for _, blk := range blocks {
for _, tx := range blk.Txns {
if tx.Hash() == hash {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(tx)
return
}
}
}
http.Error(w, "transação não encontrada", http.StatusNotFound)
}

View File

@ -0,0 +1,31 @@
package api
import (
"net/http"
"strconv"
"github.com/gorilla/mux"
)
func (h *Handler) ListBlocks(w http.ResponseWriter, r *http.Request) {
blocks, err := h.Store.ListBlocks()
if err != nil {
WriteError(w, http.StatusInternalServerError, "Erro ao listar blocos")
return
}
WriteJSON(w, blocks)
}
func (h *Handler) GetBlockByHeight(w http.ResponseWriter, r *http.Request) {
heightStr := mux.Vars(r)["height"]
height, err := strconv.Atoi(heightStr)
if err != nil {
WriteError(w, http.StatusBadRequest, "Altura inválida")
return
}
block, err := h.Store.LoadBlock(height)
if err != nil {
WriteError(w, http.StatusNotFound, "Bloco não encontrado")
return
}
WriteJSON(w, block)
}

15
internal/api/dao_api.go Normal file
View File

@ -0,0 +1,15 @@
package api
import (
"dejo_node/internal/dao"
"sync/atomic"
)
// DaoStore é acessado globalmente pelos handlers DAO.
var DaoStore *dao.ProposalStore
// MinStakePtr é o ponteiro global para o valor atual de minStake.
var MinStakePtr *atomic.Uint64
// SetMinStakeFunc permite alterar dinamicamente via DAO.
var SetMinStakeFunc func(uint64)

View File

@ -0,0 +1,84 @@
package api
import (
"encoding/json"
"net/http"
"strconv"
"github.com/gorilla/mux"
"dejo_node/internal/dao"
)
type CreateProposalRequest struct {
Title string `json:"title"`
Content string `json:"content"`
Type string `json:"type"`
Creator string `json:"creator"`
Duration int64 `json:"duration"`
}
type VoteRequest struct {
Address string `json:"address"`
Approve bool `json:"approve"`
}
func (h *Handler) CreateProposal(w http.ResponseWriter, r *http.Request) {
var req CreateProposalRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
WriteError(w, http.StatusBadRequest, "JSON inválido")
return
}
p := DaoStore.Create(req.Title, req.Content, req.Creator, dao.ProposalType(req.Type), req.Duration)
_ = DaoStore.SaveToDisk("data/proposals.db")
WriteJSON(w, p)
}
func (h *Handler) VoteProposal(w http.ResponseWriter, r *http.Request) {
idStr := mux.Vars(r)["id"]
id, err := strconv.ParseUint(idStr, 10, 64)
if err != nil {
WriteError(w, http.StatusBadRequest, "ID inválido")
return
}
var req VoteRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
WriteError(w, http.StatusBadRequest, "JSON inválido")
return
}
p, err := DaoStore.Get(id)
if err != nil {
WriteError(w, http.StatusNotFound, "Proposta não encontrada")
return
}
_ = p.Vote(req.Address, req.Approve)
p.CheckAndClose(h.snapshotStakes())
_ = DaoStore.SaveToDisk("data/proposals.db")
WriteJSON(w, p)
}
func (h *Handler) GetProposal(w http.ResponseWriter, r *http.Request) {
idStr := mux.Vars(r)["id"]
id, err := strconv.ParseUint(idStr, 10, 64)
if err != nil {
WriteError(w, http.StatusBadRequest, "ID inválido")
return
}
p, err := DaoStore.Get(id)
if err != nil {
WriteError(w, http.StatusNotFound, "Proposta não encontrada")
return
}
WriteJSON(w, p)
}
func (h *Handler) ListProposals(w http.ResponseWriter, r *http.Request) {
WriteJSON(w, DaoStore.List())
}
func (h *Handler) snapshotStakes() map[string]uint64 {
snapshot := make(map[string]uint64)
for addr, entry := range h.StakingStore.Snapshot() {
snapshot[addr] = entry.Amount
}
return snapshot
}

20
internal/api/handler.go Normal file
View File

@ -0,0 +1,20 @@
package api
import (
"dejo_node/internal/mempool"
"dejo_node/internal/staking"
"dejo_node/internal/storage"
)
type Handler struct {
Store *storage.BlockStore
StakingStore *staking.StakingStore
Mempool *mempool.Mempool
}
func NewHandler() *Handler {
return &Handler{
Store: storage.NewBlockStore("data/blocks.json"),
StakingStore: staking.NewStakingStore(),
}
}

10
internal/api/health.go Normal file
View File

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

View File

@ -0,0 +1,11 @@
package api
import (
"net/http"
)
// HandlePing responde a heartbeat dos peers
func HandlePing(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("pong"))
}

15
internal/api/liveness.go Normal file
View File

@ -0,0 +1,15 @@
package api
import (
"net/http"
)
func (h *Handler) Startup(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("started"))
}
func (h *Handler) Ready(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("ready"))
}

View File

@ -0,0 +1,91 @@
package api
import (
"context"
"net"
"net/http"
"strings"
"sync"
"time"
limiter "github.com/ulule/limiter/v3"
memory "github.com/ulule/limiter/v3/drivers/store/memory"
stdmiddleware "github.com/ulule/limiter/v3/drivers/middleware/stdlib"
)
// SybilProtector mantém IPs bloqueados temporariamente
var sybilBlocklist = struct {
mu sync.RWMutex
block map[string]time.Time
}{block: make(map[string]time.Time)}
// isBlocked verifica se um IP está na lista de bloqueio
func isBlocked(ip string) bool {
sybilBlocklist.mu.RLock()
defer sybilBlocklist.mu.RUnlock()
expire, exists := sybilBlocklist.block[ip]
return exists && time.Now().Before(expire)
}
// blockIP adiciona um IP à blocklist por X segundos
func blockIP(ip string, duration time.Duration) {
sybilBlocklist.mu.Lock()
defer sybilBlocklist.mu.Unlock()
sybilBlocklist.block[ip] = time.Now().Add(duration)
}
// extractIP extrai IP puro do RemoteAddr
func extractIP(addr string) string {
ip, _, err := net.SplitHostPort(addr)
if err != nil {
return addr
}
return ip
}
// NewRateLimiterMiddleware cria middleware de rate limiting e proteção anti-Sybil
func NewRateLimiterMiddleware() func(http.Handler) http.Handler {
rate := limiter.Rate{
Period: 1 * time.Second,
Limit: 5,
}
store := memory.NewStore()
limiterInstance := limiter.New(store, rate)
middleware := stdmiddleware.NewMiddleware(limiterInstance)
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Ignorar WebSocket
if strings.HasPrefix(r.URL.Path, "/ws") {
next.ServeHTTP(w, r)
return
}
ip := extractIP(r.RemoteAddr)
if isBlocked(ip) {
w.WriteHeader(http.StatusTooManyRequests)
WriteError(w, http.StatusTooManyRequests, "IP bloqueado temporariamente por abuso")
return
}
ctx := context.WithValue(r.Context(), "real-ip", ip)
rec := &responseRecorder{ResponseWriter: w, status: 200}
middleware.Handler(next).ServeHTTP(rec, r.WithContext(ctx))
if rec.status == http.StatusTooManyRequests {
blockIP(ip, 30*time.Second)
}
})
}
}
// responseRecorder intercepta status code para detectar excesso de requisições
type responseRecorder struct {
http.ResponseWriter
status int
}
func (rr *responseRecorder) WriteHeader(code int) {
rr.status = code
rr.ResponseWriter.WriteHeader(code)
}

42
internal/api/router.go Normal file
View File

@ -0,0 +1,42 @@
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
r.HandleFunc("/blocks", h.ListBlocks).Methods("GET")
r.HandleFunc("/blocks/{height}", h.GetBlockByHeight).Methods("GET")
// ❤️ Heartbeat
r.HandleFunc("/ping", HandlePing).Methods("GET")
return r
}

21
internal/api/server.go Normal file
View File

@ -0,0 +1,21 @@
package api
import (
"github.com/gorilla/mux"
"net/http"
"dejo_node/internal/ws"
)
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")
r.HandleFunc("/ws", ws.HandleWS).Methods("GET")
return r
}

View File

@ -0,0 +1,53 @@
package api
import (
"encoding/json"
"net/http"
"github.com/gorilla/mux"
)
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")
return
}
WriteJSON(w, info)
}

View File

@ -0,0 +1,30 @@
package api
import (
"encoding/json"
"log"
"net/http"
"dejo_node/internal/transactions"
)
func (h *Handler) SendTransaction(w http.ResponseWriter, r *http.Request) {
log.Println("📥 Nova transação recebida")
var tx transactions.Transaction
decoder := json.NewDecoder(r.Body)
if err := decoder.Decode(&tx); err != nil {
http.Error(w, "formato inválido", http.StatusBadRequest)
return
}
if tx.From == "" || tx.To == "" || tx.Value <= 0 {
http.Error(w, "transação inválida", http.StatusBadRequest)
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"})
}

22
internal/api/utils.go Normal file
View File

@ -0,0 +1,22 @@
package api
import (
"encoding/json"
"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, data interface{}) {
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(map[string]interface{}{
"success": true,
"data": data,
})
}

View File

@ -0,0 +1,13 @@
package blockchain
import "dejo_node/internal/transactions"
// Block representa um bloco da blockchain.
type Block struct {
Hash string
PreviousHash string
Timestamp int64
Transactions []transactions.Transaction
Nonce int
Finalized bool
}

27
internal/config/config.go Normal file
View File

@ -0,0 +1,27 @@
package config
import (
"crypto/ecdsa"
"sync"
)
var (
GlobalPrivateKey *ecdsa.PrivateKey
GlobalPublicKey *ecdsa.PublicKey
once sync.Once
)
// SetGlobalKeys configura a chave privada e pública globais, se ainda não estiverem definidas.
func SetGlobalKeys(priv *ecdsa.PrivateKey, pub *ecdsa.PublicKey) {
once.Do(func() {
GlobalPrivateKey = priv
GlobalPublicKey = pub
})
}
// ResetGlobalKeys limpa as chaves globais (usado em testes)
func ResetGlobalKeys() {
GlobalPrivateKey = nil
GlobalPublicKey = nil
once = sync.Once{}
}

View File

@ -0,0 +1,15 @@
package consensus
import "dejo_node/internal/transactions"
// ConsensusEngine define a interface para algoritmos de consenso.
type ConsensusEngine interface {
// ValidateBlock verifica se o bloco atende aos critérios do consenso.
ValidateBlock(block *transactions.Block) error
// SelectProposer retorna o ID do validador responsável por propor o próximo bloco.
SelectProposer(height uint64) (string, error)
// FinalizeBlock aplica qualquer regra de finalização (ex: selar, assinar, etc).
FinalizeBlock(block *transactions.Block) error
}

View File

@ -0,0 +1,15 @@
package consensus
import "dejo_node/internal/transactions"
// Engine representa um mecanismo de consenso pluggable.
type Engine interface {
// CanPropose determina se o nó atual pode propor um novo bloco
CanPropose() bool
// Finalize valida e finaliza o bloco antes de ser adicionado na cadeia
Finalize(block *transactions.Block) error
// Name retorna o nome do mecanismo de consenso ativo
Name() string
}

View File

@ -0,0 +1,72 @@
package consensus
import (
"crypto/ecdsa"
"crypto/rand"
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"math/big"
"time"
"dejo_node/internal/transactions"
)
// Finalizer encapsula a lógica de selar e assinar blocos.
type Finalizer struct {
PrivateKey *ecdsa.PrivateKey
NodeID string // opcional: ID do produtor do bloco
}
// Finalize executa a finalização de um bloco:
// - Adiciona timestamp
// - Gera hash final
// - Retorna assinatura
func (f *Finalizer) Finalize(block *transactions.Block) (string, error) {
if block == nil {
return "", errors.New("bloco nulo")
}
block.Timestamp = time.Now().Unix()
// Geração do hash usando campos existentes do bloco
hashInput := []byte(
fmt.Sprintf("%d|%s|%d|%s",
block.Index,
block.PrevHash,
block.Timestamp,
f.NodeID,
),
)
hash := sha256.Sum256(hashInput)
block.Hash = hex.EncodeToString(hash[:])
r, s, err := ecdsa.Sign(rand.Reader, f.PrivateKey, hash[:])
if err != nil {
return "", err
}
sig := append(r.Bytes(), s.Bytes()...)
signature := hex.EncodeToString(sig)
return signature, nil
}
// VerifySignature verifica se a assinatura é válida com a chave pública fornecida.
func VerifySignature(block *transactions.Block, signature string, pubKey *ecdsa.PublicKey) bool {
hash, err := hex.DecodeString(block.Hash)
if err != nil {
return false
}
sig, err := hex.DecodeString(signature)
if err != nil || len(sig) < 64 {
return false
}
r := big.NewInt(0).SetBytes(sig[:len(sig)/2])
s := big.NewInt(0).SetBytes(sig[len(sig)/2:])
return ecdsa.Verify(pubKey, hash, r, s)
}

View File

@ -0,0 +1,78 @@
package consensus
import (
"log"
"os"
"strings"
"sync"
"time"
)
type LivenessMonitor struct {
Peers []string
Status map[string]bool
Mu sync.RWMutex
SelfID string
Transport *HTTPTransport
}
func NewLivenessMonitor(transport *HTTPTransport) *LivenessMonitor {
peersEnv := os.Getenv("DEJO_PEERS")
peers := []string{}
if peersEnv != "" {
peers = strings.Split(peersEnv, ",")
}
status := make(map[string]bool)
for _, peer := range peers {
status[peer] = true
}
return &LivenessMonitor{
Peers: peers,
Status: status,
Transport: transport,
SelfID: os.Getenv("NODE_ID"),
}
}
func (l *LivenessMonitor) Start() {
interval := 5 * time.Second
if val := os.Getenv("DEJO_LIVENESS_INTERVAL"); val != "" {
if d, err := time.ParseDuration(val); err == nil {
interval = d
}
}
ticker := time.NewTicker(interval)
go func() {
for range ticker.C {
l.checkPeers()
}
}()
}
func (l *LivenessMonitor) checkPeers() {
for _, peer := range l.Peers {
err := l.Transport.PingPeer(peer)
l.Mu.Lock()
if err != nil {
if l.Status[peer] {
log.Println("⚠️ Peer", peer, "está OFFLINE")
}
l.Status[peer] = false
} else {
if !l.Status[peer] {
log.Println("✅ Peer", peer, "voltou ONLINE")
}
l.Status[peer] = true
}
l.Mu.Unlock()
}
}
func (l *LivenessMonitor) IsAlive(peer string) bool {
if peer == l.SelfID {
return true
}
l.Mu.RLock()
defer l.Mu.RUnlock()
return l.Status[peer]
}

View File

@ -0,0 +1,54 @@
package consensus
import "time"
// MessageType representa o tipo de mensagem de consenso.
type MessageType string
const (
ProposalType MessageType = "PROPOSAL"
PrevoteType MessageType = "PREVOTE"
PrecommitType MessageType = "PRECOMMIT"
)
// ConsensusMessage representa uma mensagem genérica de consenso.
type ConsensusMessage interface {
Type() MessageType
Height() uint64
Round() uint64
ValidatorID() string
Timestamp() time.Time
}
// BaseMsg contém os campos comuns entre as mensagens.
type BaseMsg struct {
MsgType MessageType
HeightVal uint64
RoundVal uint64
Validator string
Time time.Time
}
func (b BaseMsg) Type() MessageType { return b.MsgType }
func (b BaseMsg) Height() uint64 { return b.HeightVal }
func (b BaseMsg) Round() uint64 { return b.RoundVal }
func (b BaseMsg) ValidatorID() string { return b.Validator }
func (b BaseMsg) Timestamp() time.Time { return b.Time }
// ProposalMsg carrega a proposta de bloco feita por um validador.
type ProposalMsg struct {
BaseMsg
BlockHash string // Hash do bloco proposto
}
// PrevoteMsg representa um voto inicial a favor de um bloco ou nil.
type PrevoteMsg struct {
BaseMsg
BlockHash string // Pode ser "" para nil
}
// PrecommitMsg representa o voto firme para travar o bloco.
type PrecommitMsg struct {
BaseMsg
BlockHash string // Pode ser "" para nil
}

View File

@ -0,0 +1,39 @@
package consensus
import (
"sync"
)
// MessageHandler é a função chamada quando uma mensagem é recebida.
type MessageHandler func(msg ConsensusMessage)
// Transport simula o envio e recebimento de mensagens de consenso entre nós.
type Transport struct {
handlersMu sync.RWMutex
handlers map[MessageType][]MessageHandler
}
// NewTransport cria um novo transporte de mensagens de consenso.
func NewTransport() *Transport {
return &Transport{
handlers: make(map[MessageType][]MessageHandler),
}
}
// Register adiciona um handler para um tipo de mensagem.
func (t *Transport) Register(msgType MessageType, handler MessageHandler) {
t.handlersMu.Lock()
defer t.handlersMu.Unlock()
t.handlers[msgType] = append(t.handlers[msgType], handler)
}
// Broadcast envia uma mensagem para todos os handlers registrados daquele tipo.
func (t *Transport) Broadcast(msg ConsensusMessage) {
t.handlersMu.RLock()
handlers := t.handlers[msg.Type()]
t.handlersMu.RUnlock()
for _, h := range handlers {
go h(msg) // Envia de forma assíncrona
}
}

53
internal/consensus/pos.go Normal file
View File

@ -0,0 +1,53 @@
package consensus
import (
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"dejo_node/internal/transactions"
)
// PoSEngine é uma implementação simples e fake de um consenso Proof-of-Stake.
type PoSEngine struct {
validators []string
}
// NewPoSEngine cria um novo mecanismo de consenso PoS.
func NewPoSEngine(validators []string) *PoSEngine {
return &PoSEngine{
validators: validators,
}
}
// ValidateBlock verifica se o bloco tem dados válidos.
func (p *PoSEngine) ValidateBlock(block *transactions.Block) error {
if block == nil {
return errors.New("bloco nulo")
}
if block.Index == 0 && block.PrevHash != "" {
return errors.New("bloco gênese não deve ter PrevHash")
}
if len(block.Txns) == 0 {
return errors.New("bloco sem transações")
}
return nil
}
// SelectProposer seleciona pseudo-aleatoriamente um validador com base na altura.
func (p *PoSEngine) SelectProposer(height uint64) (string, error) {
if len(p.validators) == 0 {
return "", errors.New("nenhum validador registrado")
}
hash := sha256.Sum256([]byte(fmt.Sprintf("%d", height)))
index := int(hash[0]) % len(p.validators)
return p.validators[index], nil
}
// FinalizeBlock simula a finalização de bloco assinando seu hash.
func (p *PoSEngine) FinalizeBlock(block *transactions.Block) error {
hash := sha256.Sum256([]byte(fmt.Sprintf("%d:%s:%d", block.Index, block.PrevHash, len(block.Txns))))
block.Hash = hex.EncodeToString(hash[:])
return nil
}

View File

@ -0,0 +1,18 @@
package consensus
import (
"dejo_node/internal/mempool"
"dejo_node/internal/transactions"
"time"
)
func ProposeBlock(index uint64, prevHash string, pool *mempool.Mempool) *transactions.Block {
block := &transactions.Block{
Index: index,
PrevHash: prevHash,
Timestamp: time.Now().Unix(),
Txns: pool.All(),
}
block.Hash = block.CalculateHash()
return block
}

View File

@ -0,0 +1,55 @@
package consensus
import "log"
// CheckQuorum verifica se 2/3 dos validadores (por quantidade) assinaram.
func CheckQuorum(votes map[string]string, totalValidators int) (bool, string) {
voteCounts := make(map[string]int)
for _, hash := range votes {
voteCounts[hash]++
}
quorum := (2 * totalValidators) / 3
for hash, count := range voteCounts {
if count > quorum {
return true, hash
}
}
return false, ""
}
// CheckQuorumWeighted verifica se votos representam 2/3 do stake total considerando peers online.
func CheckQuorumWeighted(votes map[string]string, valSet *ValidatorSet, liveness *LivenessMonitor) (bool, string) {
totalStake := uint64(0)
for _, val := range valSet.Validators {
if liveness.IsAlive(val.Address) {
totalStake += val.Stake
}
}
required := (2 * totalStake) / 3
log.Printf("🔍 Total stake online: %d | Quórum necessário: %d\n", totalStake, required)
weighted := make(map[string]uint64)
for validator, hash := range votes {
val, ok := valSet.ValidatorByAddress(validator)
if !ok {
log.Printf("⚠️ Validador %s não encontrado no conjunto\n", validator)
continue
}
if !liveness.IsAlive(val.Address) {
log.Printf("⚠️ Validador %s está OFFLINE\n", val.Address)
continue
}
log.Printf("✅ Voto de %s para hash %s com %d tokens\n", val.Address, hash, val.Stake)
weighted[hash] += val.Stake
}
for hash, sum := range weighted {
log.Printf("📊 Hash %s recebeu %d tokens\n", hash, sum)
if sum > required {
log.Printf("🎯 Quórum atingido para hash %s\n", hash)
return true, hash
}
}
log.Println("❌ Quórum NÃO atingido")
return false, ""
}

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

View File

@ -0,0 +1,23 @@
package simple
import (
"dejo_node/internal/storage"
"dejo_node/internal/transactions"
"fmt"
)
// FinalityManager lida com a finalização de blocos.
type FinalityManager struct {
Storage storage.Storage
}
// NewFinalityManager cria uma nova instância de FinalityManager.
func NewFinalityManager(storage storage.Storage) *FinalityManager {
return &FinalityManager{Storage: storage}
}
// FinalizeBlock salva um bloco finalizado no storage.
func (fm *FinalityManager) FinalizeBlock(b *transactions.Block) error {
fmt.Println("📦 Finalizando bloco:", b.Hash)
return fm.Storage.SaveBlock(b)
}

View File

@ -0,0 +1,69 @@
package simple
import (
"dejo_node/internal/consensus"
"dejo_node/internal/transactions"
"errors"
"fmt"
"go.uber.org/zap"
)
// SimpleEngine é uma implementação básica do mecanismo de consenso.
type SimpleEngine struct {
logger *zap.SugaredLogger
}
// New cria uma nova instância do SimpleEngine
func New() consensus.Engine {
logger, _ := zap.NewProduction()
return &SimpleEngine{
logger: logger.Sugar().Named("consensus.simple"),
}
}
// CanPropose retorna true para permitir que qualquer nó proponha blocos.
func (s *SimpleEngine) CanPropose() bool {
s.logger.Debug("verificando permissão para propor bloco: permitido")
return true
}
// Finalize aplica validações de integridade ao bloco antes de ser aceito.
func (s *SimpleEngine) Finalize(block *transactions.Block) error {
if block == nil {
s.logger.Error("bloco recebido é nulo")
return errors.New("bloco nulo")
}
s.logger.Infow("finalizando bloco",
"index", block.Index,
"txns", len(block.Txns),
"hash", block.Hash,
)
if len(block.Txns) == 0 {
s.logger.Warn("bloco sem transações")
return fmt.Errorf("bloco sem transações não é permitido")
}
if block.Timestamp == 0 {
s.logger.Warn("timestamp ausente")
return fmt.Errorf("timestamp ausente no bloco")
}
hashRecalculado := block.CalculateHash()
if block.Hash != hashRecalculado {
s.logger.Errorw("hash inconsistente",
"esperado", hashRecalculado,
"recebido", block.Hash,
)
return fmt.Errorf("hash inconsistente: esperado %s, calculado %s", block.Hash, hashRecalculado)
}
s.logger.Infow("bloco finalizado com sucesso", "index", block.Index)
return nil
}
// Name retorna o identificador deste mecanismo de consenso.
func (s *SimpleEngine) Name() string {
return "SimpleEngine"
}

View File

@ -0,0 +1,28 @@
package consensus
import (
"dejo_node/internal/staking"
"log"
)
const SlashAmount = 50 // tokens penalizados
// ApplySlash penaliza validadores que não votaram corretamente.
func ApplySlash(precommits map[string]string, blockHash string, stakingStore *staking.StakingStore, validatorSet *ValidatorSet) {
for _, val := range validatorSet.Validators {
vote, voted := precommits[val.Address]
if !voted || vote != blockHash {
// Slashing
info, exists := stakingStore.GetStakeInfo(val.Address)
if exists && info.Amount >= SlashAmount {
newInfo := staking.StakeInfo{
Amount: info.Amount - SlashAmount,
Duration: uint64(info.Duration),
}
stakingStore.UpdateStakeInfo(val.Address, newInfo)
log.Printf("⚡ SLASH: Validador %s penalizado em %d tokens\n", val.Address, SlashAmount)
}
}
}
_ = stakingStore.SaveToDisk("data/staking.db")
}

View File

@ -0,0 +1,42 @@
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
}
// 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(),
}
}
// 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

@ -0,0 +1,109 @@
package consensus
import (
"bytes"
"encoding/json"
"errors"
"io"
"log"
"net/http"
"os"
"strings"
"time"
)
type HTTPTransport struct {
peers []string
handlers map[MessageType]func(ConsensusMessage)
}
func NewHTTPTransport() *HTTPTransport {
peersEnv := os.Getenv("DEJO_PEERS")
peers := []string{}
if peersEnv != "" {
peers = strings.Split(peersEnv, ",")
}
return &HTTPTransport{
peers: peers,
handlers: make(map[MessageType]func(ConsensusMessage)),
}
}
func (t *HTTPTransport) Broadcast(msg ConsensusMessage) {
data, err := json.Marshal(msg)
if err != nil {
log.Println("❌ Erro ao serializar mensagem para broadcast:", err)
return
}
for _, peer := range t.peers {
go func(peer string) {
resp, err := http.Post(peer+"/consensus", "application/json", bytes.NewReader(data))
if err != nil {
log.Println("⚠️ Erro ao enviar mensagem para", peer, "erro:", err)
return
}
resp.Body.Close()
}(peer)
}
}
func (t *HTTPTransport) Register(msgType MessageType, handler func(ConsensusMessage)) {
t.handlers[msgType] = handler
}
func (t *HTTPTransport) HandleIncoming(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
bodyBytes, err := io.ReadAll(r.Body)
if err != nil {
log.Println("❌ Erro ao ler corpo da mensagem:", err)
w.WriteHeader(http.StatusBadRequest)
return
}
var base BaseMsg
if err := json.Unmarshal(bodyBytes, &base); err != nil {
log.Println("❌ Erro ao decodificar base da mensagem:", err)
w.WriteHeader(http.StatusBadRequest)
return
}
switch base.MsgType {
case ProposalType:
var msg ProposalMsg
_ = json.Unmarshal(bodyBytes, &msg)
if h, ok := t.handlers[ProposalType]; ok {
h(msg)
}
case PrevoteType:
var msg PrevoteMsg
_ = json.Unmarshal(bodyBytes, &msg)
if h, ok := t.handlers[PrevoteType]; ok {
h(msg)
}
case PrecommitType:
var msg PrecommitMsg
_ = json.Unmarshal(bodyBytes, &msg)
if h, ok := t.handlers[PrecommitType]; ok {
h(msg)
}
default:
log.Println("⚠️ Tipo de mensagem desconhecido:", base.MsgType)
}
w.WriteHeader(http.StatusOK)
}
// PingPeer envia um ping para o peer e espera resposta.
func (t *HTTPTransport) PingPeer(peer string) error {
client := http.Client{
Timeout: 2 * time.Second,
}
resp, err := client.Get(peer + "/ping")
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return errors.New("resposta inválida ao ping")
}
return nil
}

View File

@ -0,0 +1,64 @@
package consensus
import (
"dejo_node/internal/staking"
"sort"
)
type Validator struct {
Address string
Stake uint64
}
type ValidatorSet struct {
Validators []Validator
IndexMap map[string]int
}
func NewValidatorSetFromStaking(store *staking.StakingStore, minStake uint64) *ValidatorSet {
vals := []Validator{}
storeSnapshot := store.Snapshot()
for addr, entry := range storeSnapshot {
if entry.Amount >= minStake {
vals = append(vals, Validator{Address: addr, Stake: entry.Amount})
}
}
sort.Slice(vals, func(i, j int) bool {
return vals[i].Stake > vals[j].Stake
})
idx := make(map[string]int)
for i, v := range vals {
idx[v.Address] = i
}
return &ValidatorSet{Validators: vals, IndexMap: idx}
}
func (vs *ValidatorSet) SelectProposer(height uint64) *Validator {
if len(vs.Validators) == 0 {
return nil
}
index := int(height % uint64(len(vs.Validators)))
return &vs.Validators[index]
}
func (vs *ValidatorSet) IsValidator(address string) bool {
_, ok := vs.IndexMap[address]
return ok
}
func (vs *ValidatorSet) TotalStake() uint64 {
sum := uint64(0)
for _, v := range vs.Validators {
sum += v.Stake
}
return sum
}
func (vs *ValidatorSet) ValidatorByAddress(addr string) (Validator, bool) {
i, ok := vs.IndexMap[addr]
if !ok {
return Validator{}, false
}
return vs.Validators[i], true
}

21
internal/crypto/crypto.go Normal file
View File

@ -0,0 +1,21 @@
package crypto
import (
"crypto/ed25519"
"crypto/sha256"
"encoding/hex"
"encoding/json"
)
// GenerateHash gera um hash SHA-256 de qualquer estrutura
func GenerateHash(v interface{}) string {
b, _ := json.Marshal(v)
h := sha256.Sum256(b)
return hex.EncodeToString(h[:])
}
// SignMessage assina uma string com uma chave privada
func SignMessage(message string, privateKey ed25519.PrivateKey) string {
sig := ed25519.Sign(privateKey, []byte(message))
return hex.EncodeToString(sig)
}

7
internal/crypto/keys.go Normal file
View File

@ -0,0 +1,7 @@
package crypto
import "crypto/ecdsa"
// GlobalPrivateKey é usada temporariamente como referência global para testes e simulações.
// Em produção, isso deve ser gerenciado de forma segura.
var GlobalPrivateKey *ecdsa.PrivateKey

View File

@ -0,0 +1,26 @@
package crypto
import "fmt"
// PQSigner é um placeholder para um algoritmo de assinatura pós-quântico (ex: Dilithium, Falcon)
type PQSigner struct{}
func (s *PQSigner) GenerateKeys() (string, string, error) {
return "", "", fmt.Errorf("PQSigner ainda não implementado")
}
func (s *PQSigner) Sign(message, privKey string) (string, error) {
return "", fmt.Errorf("PQSigner ainda não implementado")
}
func (s *PQSigner) Verify(message, signature, pubKey string) bool {
return false
}
func (s *PQSigner) Name() string {
return "pq"
}
func init() {
Register(&PQSigner{})
}

27
internal/crypto/signer.go Normal file
View File

@ -0,0 +1,27 @@
package crypto
import "errors"
// Signer representa qualquer esquema de assinatura digital
// (ex: ECDSA, pós-quântico, etc)
type Signer interface {
GenerateKeys() (privKey string, pubKey string, err error)
Sign(message, privKey string) (string, error)
Verify(message, signature, pubKey string) bool
Name() string
}
var registered = make(map[string]Signer)
// Register um novo esquema de assinatura
func Register(signer Signer) {
registered[signer.Name()] = signer
}
// Get retorna o esquema de assinatura por nome
func Get(name string) (Signer, error) {
if s, ok := registered[name]; ok {
return s, nil
}
return nil, errors.New("assinador não encontrado")
}

104
internal/dao/proposal.go Normal file
View File

@ -0,0 +1,104 @@
package dao
import (
"encoding/json"
"log"
"sync"
"time"
)
type ProposalType string
const (
GenericProposal ProposalType = "GENERIC"
ParamChangeProposal ProposalType = "PARAM_CHANGE"
)
type Proposal struct {
ID uint64
Title string
Content string
Type ProposalType
Creator string
CreatedAt int64
Duration int64 // segundos até expiração
Votes map[string]bool
Closed bool
Approved bool
mu sync.Mutex
}
func NewProposal(id uint64, title, content, creator string, typ ProposalType, duration int64) *Proposal {
return &Proposal{
ID: id,
Title: title,
Content: content,
Type: typ,
Creator: creator,
CreatedAt: time.Now().Unix(),
Duration: duration,
Votes: make(map[string]bool),
Closed: false,
Approved: false,
}
}
func (p *Proposal) Vote(address string, approve bool) error {
p.mu.Lock()
defer p.mu.Unlock()
if p.Closed {
return nil
}
p.Votes[address] = approve
return nil
}
func (p *Proposal) Tally(stakes map[string]uint64) (yes uint64, no uint64, total uint64) {
p.mu.Lock()
defer p.mu.Unlock()
for addr, vote := range p.Votes {
stake := stakes[addr]
total += stake
if vote {
yes += stake
} else {
no += stake
}
}
return
}
func (p *Proposal) CheckAndClose(stakes map[string]uint64) {
if p.Closed {
return
}
if time.Now().Unix() >= p.CreatedAt+p.Duration {
yes, _, total := p.Tally(stakes)
if yes*100/total > 66 {
p.Approved = true
}
p.Closed = true
if p.Approved {
p.Execute()
}
}
}
// Execute aplica os efeitos da proposta, se for do tipo que altera estado
func (p *Proposal) Execute() {
if p.Type == ParamChangeProposal {
var payload struct {
Target string `json:"target"`
Value uint64 `json:"value"`
}
err := json.Unmarshal([]byte(p.Content), &payload)
if err != nil {
log.Printf("❌ Erro ao executar proposta PARAM_CHANGE: %v", err)
return
}
// Exemplo: muda parâmetro global (placeholder)
log.Printf("⚙️ Aplicando PARAM_CHANGE: %s = %d", payload.Target, payload.Value)
// Aqui você aplicaria no config real
}
}

78
internal/dao/store.go Normal file
View File

@ -0,0 +1,78 @@
package dao
import (
"encoding/gob"
"errors"
"os"
"sync"
)
type ProposalStore struct {
mu sync.RWMutex
proposals map[uint64]*Proposal
nextID uint64
}
func NewProposalStore() *ProposalStore {
return &ProposalStore{
proposals: make(map[uint64]*Proposal),
nextID: 1,
}
}
func (ps *ProposalStore) Create(title, content, creator string, typ ProposalType, duration int64) *Proposal {
ps.mu.Lock()
defer ps.mu.Unlock()
id := ps.nextID
ps.nextID++
p := NewProposal(id, title, content, creator, typ, duration)
ps.proposals[id] = p
return p
}
func (ps *ProposalStore) Get(id uint64) (*Proposal, error) {
ps.mu.RLock()
defer ps.mu.RUnlock()
p, ok := ps.proposals[id]
if !ok {
return nil, errors.New("proposta não encontrada")
}
return p, nil
}
func (ps *ProposalStore) List() []*Proposal {
ps.mu.RLock()
defer ps.mu.RUnlock()
var list []*Proposal
for _, p := range ps.proposals {
list = append(list, p)
}
return list
}
func (ps *ProposalStore) SaveToDisk(path string) error {
ps.mu.RLock()
defer ps.mu.RUnlock()
f, err := os.Create(path)
if err != nil {
return err
}
defer f.Close()
enc := gob.NewEncoder(f)
return enc.Encode(ps)
}
func (ps *ProposalStore) LoadFromDisk(path string) error {
ps.mu.Lock()
defer ps.mu.Unlock()
f, err := os.Open(path)
if err != nil {
if os.IsNotExist(err) {
return nil
}
return err
}
defer f.Close()
dec := gob.NewDecoder(f)
return dec.Decode(ps)
}

View File

@ -0,0 +1,25 @@
package mempool
import "dejo_node/internal/transactions"
type Mempool struct {
transactions []*transactions.Transaction
}
func NewMempool() *Mempool {
return &Mempool{
transactions: []*transactions.Transaction{},
}
}
func (m *Mempool) Add(tx *transactions.Transaction) {
m.transactions = append(m.transactions, tx)
}
func (m *Mempool) All() []*transactions.Transaction {
return m.transactions
}
func (m *Mempool) Clear() {
m.transactions = []*transactions.Transaction{}
}

76
internal/miner/miner.go Normal file
View File

@ -0,0 +1,76 @@
package miner
import (
"dejo_node/internal/consensus"
"dejo_node/internal/storage"
"dejo_node/internal/transactions"
"dejo_node/internal/ws"
"fmt"
"time"
)
// Miner representa o componente responsável por propor blocos válidos.
type Miner struct {
Engine consensus.Engine
BlockStore *storage.BlockStore
Mempool *transactions.Mempool
}
// New cria uma nova instância de minerador
func New(engine consensus.Engine, store *storage.BlockStore, mempool *transactions.Mempool) *Miner {
return &Miner{
Engine: engine,
BlockStore: store,
Mempool: mempool,
}
}
// MineNextBlock propõe, valida e armazena um novo bloco se possível
func (m *Miner) MineNextBlock() (*transactions.Block, error) {
if !m.Engine.CanPropose() {
return nil, fmt.Errorf("este nó não tem permissão para propor blocos")
}
txns := m.Mempool.Pending()
if len(txns) == 0 {
return nil, fmt.Errorf("nenhuma transação pendente para minerar")
}
lastBlock, err := m.BlockStore.GetLatestBlock()
index := uint64(0)
prevHash := ""
if err == nil {
index = lastBlock.Index + 1
prevHash = lastBlock.Hash
}
blk := &transactions.Block{
Index: index,
Timestamp: time.Now().Unix(),
PrevHash: prevHash,
Txns: txns,
}
blk.ComputeHash()
if err := m.Engine.Finalize(blk); err != nil {
return nil, fmt.Errorf("bloco rejeitado pelo consenso: %w", err)
}
if err := m.BlockStore.SaveBlock(blk); err != nil {
return nil, fmt.Errorf("erro ao salvar bloco: %w", err)
}
// Emitir eventos WebSocket
ws.Emit("newBlock", map[string]any{
"index": blk.Index,
"hash": blk.Hash,
})
for _, tx := range blk.Txns {
ws.Emit("txConfirmed", map[string]any{
"hash": tx.Hash(),
})
}
m.Mempool.Clear()
return blk, nil
}

View File

@ -0,0 +1,57 @@
package monitor
import (
"log"
"net/http"
"os"
"strings"
"sync"
"time"
)
var (
peers = make(map[string]bool)
peersLock sync.RWMutex
heartbeatFreq = 3 * time.Second
)
func StartHeartbeatMonitor() {
peerList := strings.Split(os.Getenv("DEJO_PEERS"), ",")
for _, p := range peerList {
peers[p] = false // Inicialmente offline
}
go func() {
ticker := time.NewTicker(heartbeatFreq)
for range ticker.C {
checkPeers(peerList)
}
}()
}
func checkPeers(peerList []string) {
for _, peer := range peerList {
go func(p string) {
resp, err := http.Get(p + "/ping")
peersLock.Lock()
defer peersLock.Unlock()
if err != nil || resp.StatusCode != http.StatusOK {
if peers[p] {
log.Printf("⚠️ Peer %s está OFFLINE", p)
}
peers[p] = false
} else {
if !peers[p] {
log.Printf("✅ Peer %s voltou ONLINE", p)
}
peers[p] = true
}
} (peer)
}
}
func IsPeerOnline(peer string) bool {
peersLock.RLock()
defer peersLock.RUnlock()
return peers[peer]
}

View File

@ -0,0 +1,43 @@
package monitor
import (
"sync"
"time"
)
type Monitor struct {
peers map[string]bool
mutex sync.RWMutex
heartbeatFn func(peer string) bool
}
func NewMonitor(heartbeatFn func(peer string) bool) *Monitor {
return &Monitor{
peers: make(map[string]bool),
heartbeatFn: heartbeatFn,
}
}
func (m *Monitor) Start(peers []string) {
for _, peer := range peers {
go func(p string) {
for {
m.updateStatus(p)
time.Sleep(5 * time.Second)
}
}(peer)
}
}
func (m *Monitor) updateStatus(peer string) {
alive := m.heartbeatFn(peer)
m.mutex.Lock()
defer m.mutex.Unlock()
m.peers[peer] = alive
}
func (m *Monitor) IsPeerOnline(peer string) bool {
m.mutex.RLock()
defer m.mutex.RUnlock()
return m.peers[peer]
}

View File

@ -0,0 +1,61 @@
package monitoring
import (
"log"
"net/http"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
var (
txCounter = prometheus.NewCounter(
prometheus.CounterOpts{
Name: "dejo_tx_total",
Help: "Total de transações recebidas pelo nó",
},
)
finalizedBlocks = prometheus.NewCounter(
prometheus.CounterOpts{
Name: "dejo_finalized_blocks_total",
Help: "Total de blocos finalizados pelo nó",
},
)
uptimeGauge = prometheus.NewGaugeFunc(
prometheus.GaugeOpts{
Name: "dejo_uptime_seconds",
Help: "Tempo desde que o nó foi iniciado",
},
func() float64 {
return time.Since(startTime).Seconds()
},
)
)
var startTime = time.Now()
// Init inicializa o endpoint /metrics
func Init() {
prometheus.MustRegister(txCounter)
prometheus.MustRegister(finalizedBlocks)
prometheus.MustRegister(uptimeGauge)
go func() {
http.Handle("/metrics", promhttp.Handler())
log.Println("📊 Endpoint de métricas disponível em /metrics")
http.ListenAndServe(":9100", nil)
}()
}
// IncTxCount incrementa o total de transações recebidas
func IncTxCount() {
txCounter.Inc()
}
// IncFinalizedBlocks incrementa o total de blocos finalizados
func IncFinalizedBlocks() {
finalizedBlocks.Inc()
}

View File

@ -0,0 +1,59 @@
package oracle
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"time"
)
// OracleData representa um dado externo coletado via oráculo
type OracleData struct {
Source string `json:"source"`
Key string `json:"key"`
Value float64 `json:"value"`
Time time.Time `json:"timestamp"`
}
// FetchFromMock simula obtenção de dados de um oráculo externo (mock)
func FetchFromMock(key string) (*OracleData, error) {
mockPrices := map[string]float64{
"USD-BRL": 5.06,
"ETH-BRL": 18732.42,
"IMOVEL-ABC123": 950000.00,
}
val, ok := mockPrices[key]
if !ok {
return nil, errors.New("dado não encontrado no mock")
}
return &OracleData{
Source: "mock",
Key: key,
Value: val,
Time: time.Now(),
}, nil
}
// FetchFromCoinGecko busca o preço de uma moeda em relação ao BRL
func FetchFromCoinGecko(coin string) (*OracleData, error) {
url := fmt.Sprintf("https://api.coingecko.com/api/v3/simple/price?ids=%s&vs_currencies=brl", coin)
resp, err := http.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var parsed map[string]map[string]float64
if err := json.NewDecoder(resp.Body).Decode(&parsed); err != nil {
return nil, err
}
price := parsed[coin]["brl"]
return &OracleData{
Source: "coingecko",
Key: coin + "-BRL",
Value: price,
Time: time.Now(),
}, nil
}

View File

@ -0,0 +1,20 @@
package oracle
import (
"testing"
)
func TestFetchFromMock(t *testing.T) {
d, err := FetchFromMock("USD-BRL")
if err != nil {
t.Fatalf("Erro ao buscar mock: %v", err)
}
if d.Key != "USD-BRL" || d.Value <= 0 {
t.Errorf("Valor inesperado do mock: %+v", d)
}
_, err = FetchFromMock("INVALIDO")
if err == nil {
t.Error("Esperado erro ao buscar chave inválida no mock")
}
}

49
internal/oracle/store.go Normal file
View File

@ -0,0 +1,49 @@
package oracle
import (
"sync"
)
// Store mantém os últimos dados válidos dos oráculos e um pequeno histórico
type Store struct {
mu sync.RWMutex
latest map[string]*OracleData
history map[string][]*OracleData
maxHist int
}
// NewStore cria uma nova instância de armazenamento de oráculos
func NewStore(maxHistory int) *Store {
return &Store{
latest: make(map[string]*OracleData),
history: make(map[string][]*OracleData),
maxHist: maxHistory,
}
}
// Save registra um novo dado e atualiza o histórico
func (s *Store) Save(data *OracleData) {
s.mu.Lock()
defer s.mu.Unlock()
s.latest[data.Key] = data
s.history[data.Key] = append(s.history[data.Key], data)
if len(s.history[data.Key]) > s.maxHist {
s.history[data.Key] = s.history[data.Key][len(s.history[data.Key])-s.maxHist:]
}
}
// Get retorna o último dado registrado
func (s *Store) Get(key string) *OracleData {
s.mu.RLock()
defer s.mu.RUnlock()
return s.latest[key]
}
// History retorna o histórico completo de um dado
func (s *Store) History(key string) []*OracleData {
s.mu.RLock()
defer s.mu.RUnlock()
return s.history[key]
}

View File

@ -0,0 +1,30 @@
package oracle
import "testing"
func TestStore_SaveAndGet(t *testing.T) {
s := NewStore(3)
d := &OracleData{Key: "ETH-BRL", Value: 10000.0}
s.Save(d)
if got := s.Get("ETH-BRL"); got == nil || got.Value != 10000.0 {
t.Error("falha ao buscar valor salvo")
}
}
func TestStore_History(t *testing.T) {
s := NewStore(2)
s.Save(&OracleData{Key: "ETH-BRL", Value: 1})
s.Save(&OracleData{Key: "ETH-BRL", Value: 2})
s.Save(&OracleData{Key: "ETH-BRL", Value: 3}) // deve descartar o primeiro
hist := s.History("ETH-BRL")
if len(hist) != 2 {
t.Errorf("esperado 2 entradas no histórico, obtido %d", len(hist))
}
if hist[0].Value != 2 || hist[1].Value != 3 {
t.Error("histórico não está correto")
}
}

View File

@ -0,0 +1,45 @@
package oracle
import (
"fmt"
"math"
"time"
)
// ValidateMajority recebe múltiplas leituras e tenta achar um consenso baseado na maioria simples.
func ValidateMajority(data []*OracleData, tolerance float64) (*OracleData, error) {
if len(data) == 0 {
return nil, fmt.Errorf("nenhum dado recebido para validar")
}
count := make(map[float64]int)
latest := make(map[float64]time.Time)
for _, d := range data {
rounded := math.Round(d.Value/tolerance) * tolerance // agrupar valores similares
count[rounded]++
if d.Time.After(latest[rounded]) {
latest[rounded] = d.Time
}
}
// Encontrar o valor com mais votos
var maxVal float64
var maxCount int
for val, c := range count {
if c > maxCount {
maxVal = val
maxCount = c
}
}
// Retornar o OracleData mais recente com esse valor
for _, d := range data {
rounded := math.Round(d.Value/tolerance) * tolerance
if rounded == maxVal && d.Time.Equal(latest[maxVal]) {
return d, nil
}
}
return nil, fmt.Errorf("nenhum consenso válido encontrado")
}

View File

@ -0,0 +1,28 @@
package oracle
import (
"testing"
"time"
)
func TestValidateMajority(t *testing.T) {
now := time.Now()
d1 := &OracleData{Key: "USD-BRL", Value: 5.00, Time: now}
d2 := &OracleData{Key: "USD-BRL", Value: 5.01, Time: now.Add(-1 * time.Minute)}
d3 := &OracleData{Key: "USD-BRL", Value: 4.99, Time: now.Add(-2 * time.Minute)}
res, err := ValidateMajority([]*OracleData{d1, d2, d3}, 0.05)
if err != nil {
t.Fatalf("Falha ao validar maioria: %v", err)
}
if res == nil || res.Value == 0 {
t.Error("Valor inválido retornado pelo consenso")
}
// sem consenso
d4 := &OracleData{Key: "USD-BRL", Value: 3.0, Time: now}
_, err = ValidateMajority([]*OracleData{d1, d4}, 0.001)
if err == nil {
t.Error("Esperado erro por falta de consenso")
}
}

52
internal/p2p/dht.go Normal file
View File

@ -0,0 +1,52 @@
package p2p
import (
"context"
"fmt"
"time"
dht "github.com/libp2p/go-libp2p-kad-dht"
routingdiscovery "github.com/libp2p/go-libp2p/p2p/discovery/routing"
)
const rendezvousString = "dejo-global"
// InitDHT inicializa o Kademlia DHT e ativa descoberta global de peers.
func (p *P2PNode) InitDHT(ctx context.Context) error {
d, err := dht.New(ctx, p.Host)
if err != nil {
return fmt.Errorf("erro ao iniciar DHT: %w", err)
}
if err := d.Bootstrap(ctx); err != nil {
return fmt.Errorf("erro ao bootstrap DHT: %w", err)
}
routing := routingdiscovery.NewRoutingDiscovery(d)
_, err = routing.Advertise(ctx, rendezvousString)
if err != nil {
return fmt.Errorf("erro ao anunciar no DHT: %w", err)
}
fmt.Println("📡 Anunciado no DHT com tag:", rendezvousString)
go func() {
for {
peers, err := routing.FindPeers(ctx, rendezvousString)
if err != nil {
fmt.Println("Erro ao buscar peers:", err)
continue
}
for pinfo := range peers {
if pinfo.ID == p.Host.ID() {
continue // ignora si mesmo
}
fmt.Println("🌍 Peer encontrado via DHT:", pinfo.ID)
_ = p.Host.Connect(ctx, pinfo)
}
time.Sleep(10 * time.Second)
}
}()
return nil
}

66
internal/p2p/host.go Normal file
View File

@ -0,0 +1,66 @@
package p2p
import (
"crypto/rand"
"fmt"
"log"
"github.com/libp2p/go-libp2p"
crypto "github.com/libp2p/go-libp2p/core/crypto"
hostlib "github.com/libp2p/go-libp2p/core/host"
"github.com/libp2p/go-libp2p/core/network"
"github.com/libp2p/go-libp2p/core/peer"
ma "github.com/multiformats/go-multiaddr"
)
// NewSecureHost cria um nó P2P com proteção básica anti-Sybil/DDoS
func NewSecureHost(port int) (hostlib.Host, error) {
// Gera par de chaves (identidade)
priv, pub, err := crypto.GenerateKeyPairWithReader(crypto.Ed25519, 2048, rand.Reader)
if err != nil {
return nil, fmt.Errorf("falha ao gerar chave: %v", err)
}
pid, err := peer.IDFromPublicKey(pub)
if err != nil {
return nil, fmt.Errorf("erro ao obter PeerID: %v", err)
}
addr, _ := ma.NewMultiaddr(fmt.Sprintf("/ip4/0.0.0.0/tcp/%d", port))
h, err := libp2p.New(
libp2p.ListenAddrs(addr),
libp2p.Identity(priv),
)
if err != nil {
return nil, fmt.Errorf("erro ao criar host libp2p: %v", err)
}
log.Printf("✅ Host P2P criado: %s (%s)\n", pid.String(), addr)
// Proteção: loga e valida conexões
h.Network().Notify(&connLogger{})
return h, nil
}
// connLogger registra e controla conexões suspeitas
type connLogger struct{}
func (c *connLogger) Connected(n network.Network, conn network.Conn) {
if err := LimitConnections(conn); err != nil {
log.Printf("🚫 Conexão rejeitada: %s (%v)", conn.RemotePeer(), err)
conn.Close()
return
}
log.Printf("🔗 Peer conectado: %s (%s)", conn.RemotePeer(), conn.RemoteMultiaddr())
}
func (c *connLogger) Disconnected(n network.Network, conn network.Conn) {
log.Printf("❌ Peer desconectado: %s", conn.RemotePeer())
ClearConnection(conn)
}
func (c *connLogger) OpenedStream(n network.Network, s network.Stream) {}
func (c *connLogger) ClosedStream(n network.Network, s network.Stream) {}
func (c *connLogger) Listen(n network.Network, addr ma.Multiaddr) {}
func (c *connLogger) ListenClose(n network.Network, addr ma.Multiaddr) {}

58
internal/p2p/limiter.go Normal file
View File

@ -0,0 +1,58 @@
package p2p
import (
"net"
"sync"
"time"
"github.com/libp2p/go-libp2p/core/network"
)
const (
MaxConnsPerIP = 5
MinReconnectGap = 10 * time.Second
)
var (
ipConnCount = make(map[string]int)
peerLastSeen = make(map[string]time.Time)
limiterMu sync.Mutex
)
// LimitConnections implementa proteção básica anti-DDoS/Sybil
func LimitConnections(conn network.Conn) error {
limiterMu.Lock()
defer limiterMu.Unlock()
ip, _, err := net.SplitHostPort(conn.RemoteMultiaddr().String())
if err != nil {
return nil // fallback: não bloqueia
}
ipConnCount[ip]++
if ipConnCount[ip] > MaxConnsPerIP {
return network.ErrReset
}
peerID := conn.RemotePeer().String()
last := peerLastSeen[peerID]
if time.Since(last) < MinReconnectGap {
return network.ErrReset
}
peerLastSeen[peerID] = time.Now()
return nil
}
// ClearConnection cleanup quando o peer desconecta
func ClearConnection(conn network.Conn) {
limiterMu.Lock()
defer limiterMu.Unlock()
ip, _, err := net.SplitHostPort(conn.RemoteMultiaddr().String())
if err == nil {
ipConnCount[ip]--
if ipConnCount[ip] <= 0 {
delete(ipConnCount, ip)
}
}
}

16
internal/p2p/network.go Normal file
View File

@ -0,0 +1,16 @@
package p2p
import (
"log"
)
// StartNetwork inicializa a rede P2P com host seguro
func StartNetwork(port int) {
h, err := NewSecureHost(port)
if err != nil {
log.Fatalf("Erro ao iniciar host P2P: %v", err)
}
log.Printf("🌐 Rede P2P inicializada com sucesso: PeerID %s\n", h.ID())
select {} // Mantém processo vivo
}

81
internal/p2p/p2p.go Normal file
View File

@ -0,0 +1,81 @@
package p2p
import (
"context"
"crypto/rand"
"fmt"
host "github.com/libp2p/go-libp2p/core/host"
libp2p "github.com/libp2p/go-libp2p"
"github.com/libp2p/go-libp2p/core/crypto"
"github.com/libp2p/go-libp2p/core/network"
"github.com/libp2p/go-libp2p/core/peer"
)
const ProtocolID = "/dejo/1.0.0"
// P2PNode representa um nó da rede P2P da DEJO.
type P2PNode struct {
Host host.Host
PeerChan chan peer.AddrInfo
}
// NewP2PNode cria um novo nó libp2p.
func NewP2PNode(ctx context.Context) (*P2PNode, error) {
priv, _, _ := crypto.GenerateKeyPairWithReader(crypto.Ed25519, -1, rand.Reader)
h, err := libp2p.New(
libp2p.Identity(priv),
libp2p.ListenAddrStrings("/ip4/0.0.0.0/tcp/0"),
)
if err != nil {
return nil, err
}
node := &P2PNode{
Host: h,
PeerChan: make(chan peer.AddrInfo),
}
// Handler para conexões recebidas
h.SetStreamHandler(ProtocolID, func(s network.Stream) {
buf := make([]byte, 1024)
s.Read(buf)
fmt.Printf("📡 Recebido: %s\n", string(buf))
s.Close()
})
return node, nil
}
// HandlePeerFound é chamado quando um novo peer é descoberto.
func (p *P2PNode) HandlePeerFound(pi peer.AddrInfo) {
fmt.Println("👋 Novo peer descoberto:", pi.ID)
p.PeerChan <- pi
}
// ConnectToPeers tenta se conectar aos peers encontrados.
func (p *P2PNode) ConnectToPeers(ctx context.Context) {
go func() {
for pi := range p.PeerChan {
if err := p.Host.Connect(ctx, pi); err != nil {
fmt.Println("Erro ao conectar ao peer:", err)
} else {
fmt.Println("🔗 Conectado ao peer:", pi.ID)
}
}
}()
}
// Broadcast envia uma mensagem para todos os peers conectados.
func (p *P2PNode) Broadcast(msg string) {
for _, c := range p.Host.Network().Peers() {
stream, err := p.Host.NewStream(context.Background(), c, ProtocolID)
if err != nil {
fmt.Println("Erro ao abrir stream:", err)
continue
}
stream.Write([]byte(msg))
stream.Close()
}
}

35
internal/p2p/p2p_test.go Normal file
View File

@ -0,0 +1,35 @@
package p2p_test
import (
"context"
"dejo_node/internal/p2p"
"testing"
"time"
)
func TestP2PNode_DHT(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
nodeA, err := p2p.NewP2PNode(ctx)
if err != nil {
t.Fatalf("falha ao criar node A: %v", err)
}
nodeA.ConnectToPeers(ctx)
if err := nodeA.InitDHT(ctx); err != nil {
t.Fatalf("erro ao iniciar DHT no node A: %v", err)
}
nodeB, err := p2p.NewP2PNode(ctx)
if err != nil {
t.Fatalf("falha ao criar node B: %v", err)
}
nodeB.ConnectToPeers(ctx)
if err := nodeB.InitDHT(ctx); err != nil {
t.Fatalf("erro ao iniciar DHT no node B: %v", err)
}
time.Sleep(5 * time.Second)
nodeB.Broadcast("msg: test from B to A")
time.Sleep(5 * time.Second)
}

View File

@ -0,0 +1,19 @@
package staking
import (
"math"
"time"
)
// CalculateRewards calcula as recompensas baseadas em tempo e APR.
// Exemplo: APR 12% ao ano = 0.01 ao mês, 0.00033 ao dia.
func CalculateRewards(amount uint64, startTime int64, apr float64) uint64 {
daysStaked := float64(time.Now().Unix()-startTime) / (60 * 60 * 24)
if daysStaked <= 0 {
return 0
}
// Fórmula de juros simples: R = P * i * t
interest := float64(amount) * (apr / 365.0) * daysStaked
return uint64(math.Floor(interest))
}

82
internal/staking/store.go Normal file
View File

@ -0,0 +1,82 @@
package staking
import (
"encoding/gob"
"os"
"sync"
)
type StakeInfo struct {
Amount uint64
Duration uint64
}
type StakingStore struct {
data map[string]StakeInfo
mu sync.RWMutex
}
func NewStakingStore() *StakingStore {
return &StakingStore{
data: make(map[string]StakeInfo),
}
}
func (s *StakingStore) Stake(address string, amount uint64, duration uint64) error {
s.mu.Lock()
defer s.mu.Unlock()
s.data[address] = StakeInfo{Amount: amount, Duration: duration}
return nil
}
func (s *StakingStore) Unstake(address string) error {
s.mu.Lock()
defer s.mu.Unlock()
delete(s.data, address)
return nil
}
func (s *StakingStore) GetStakeInfo(address string) (StakeInfo, bool) {
s.mu.RLock()
defer s.mu.RUnlock()
info, ok := s.data[address]
return info, ok
}
func (s *StakingStore) UpdateStakeInfo(address string, info StakeInfo) {
s.mu.Lock()
defer s.mu.Unlock()
s.data[address] = info
}
func (s *StakingStore) Snapshot() map[string]StakeInfo {
s.mu.RLock()
defer s.mu.RUnlock()
clone := make(map[string]StakeInfo)
for k, v := range s.data {
clone[k] = v
}
return clone
}
func (s *StakingStore) SaveToDisk(filename string) error {
s.mu.RLock()
defer s.mu.RUnlock()
file, err := os.Create(filename)
if err != nil {
return err
}
defer file.Close()
encoder := gob.NewEncoder(file)
return encoder.Encode(s.data)
}
func (s *StakingStore) LoadFromDisk(filename string) error {
file, err := os.Open(filename)
if err != nil {
return err
}
defer file.Close()
decoder := gob.NewDecoder(file)
return decoder.Decode(&s.data)
}

8
internal/state/apply.go Normal file
View File

@ -0,0 +1,8 @@
package state
import (
"dejo_node/internal/staking"
)
var GlobalState = NewState()
var GlobalStakingStore = staking.NewStakingStore()

7
internal/state/domain.go Normal file
View File

@ -0,0 +1,7 @@
package state
func (s *State) GetBalance(addr string) uint64 {
s.mu.RLock()
defer s.mu.RUnlock()
return s.Balances[addr]
}

99
internal/state/state.go Normal file
View File

@ -0,0 +1,99 @@
package state
import (
"encoding/gob"
"fmt"
"os"
"sync"
"dejo_node/internal/staking"
"dejo_node/internal/transactions"
)
type State struct {
mu sync.RWMutex
Balances map[string]uint64
}
func NewState() *State {
return &State{
Balances: make(map[string]uint64),
}
}
func (s *State) Apply(tx *transactions.Transaction) error {
s.mu.Lock()
defer s.mu.Unlock()
if tx.From == tx.To {
return fmt.Errorf("transação inválida: origem = destino")
}
if tx.Value <= 0 {
return fmt.Errorf("valor zero não permitido")
}
fromBal := s.Balances[tx.From]
if fromBal < uint64(tx.Value) {
return fmt.Errorf("saldo insuficiente: %s tem %d, precisa de %f", tx.From, fromBal, tx.Value)
}
s.Balances[tx.From] -= uint64(tx.Value)
s.Balances[tx.To] += uint64(tx.Value)
return nil
}
func (s *State) Transfer(from, to string, amount uint64) error {
s.mu.Lock()
defer s.mu.Unlock()
if s.Balances[from] < amount {
return fmt.Errorf("saldo insuficiente")
}
s.Balances[from] -= amount
s.Balances[to] += amount
return nil
}
func (s *State) Mint(to string, amount uint64) {
s.mu.Lock()
defer s.mu.Unlock()
s.Balances[to] += amount
}
func (s *State) Stake(address string, amount uint64, dur int64) error {
if err := s.Transfer(address, "staking_pool", amount); err != nil {
return err
}
return staking.NewStakingStore().Stake(address, amount, uint64(dur))
}
func (s *State) Unstake(address string) error {
s.mu.Lock()
defer s.mu.Unlock()
if err := staking.NewStakingStore().Unstake(address); err != nil {
return err
}
s.Balances[address] += 1000 // Exemplo: devolve saldo fixo
return nil
}
func (s *State) SaveToDisk(path string) error {
f, err := os.Create(path)
if err != nil {
return err
}
defer f.Close()
enc := gob.NewEncoder(f)
return enc.Encode(s.Balances)
}
func (s *State) LoadFromDisk(path string) error {
f, err := os.Open(path)
if err != nil {
if os.IsNotExist(err) {
s.Balances = make(map[string]uint64)
return nil
}
return err
}
defer f.Close()
dec := gob.NewDecoder(f)
return dec.Decode(&s.Balances)
}

View File

@ -0,0 +1,24 @@
package storage
import (
"errors"
"os"
"path/filepath"
"strconv"
)
const snapshotFile = "state_snapshot.txt"
func (bs *BlockStore) SetSnapshotHeight(index uint64) error {
file := filepath.Join(bs.Path, snapshotFile)
return os.WriteFile(file, []byte(strconv.FormatUint(index, 10)), 0644)
}
func (bs *BlockStore) GetSnapshotHeight() (uint64, error) {
file := filepath.Join(bs.Path, snapshotFile)
data, err := os.ReadFile(file)
if err != nil {
return 0, errors.New("nenhum snapshot salvo")
}
return strconv.ParseUint(string(data), 10, 64)
}

View File

@ -0,0 +1,8 @@
package storage
import "dejo_node/internal/transactions"
// Storage define a interface para persistência de blocos.
type Storage interface {
SaveBlock(block *transactions.Block) error
}

86
internal/storage/store.go Normal file
View File

@ -0,0 +1,86 @@
package storage
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"sort"
"strconv"
"dejo_node/internal/transactions"
)
type BlockStore struct {
Path string
}
func NewBlockStore(path string) *BlockStore {
_ = os.MkdirAll(path, 0755)
return &BlockStore{Path: path}
}
func (s *BlockStore) SaveBlock(block *transactions.Block) error {
file := filepath.Join(s.Path, fmt.Sprintf("block_%03d.json", block.Index))
data, err := json.MarshalIndent(block, "", " ")
if err != nil {
return err
}
return ioutil.WriteFile(file, data, 0644)
}
func (s *BlockStore) LoadBlock(height int) (*transactions.Block, error) {
file := filepath.Join(s.Path, fmt.Sprintf("block_%03d.json", height))
data, err := ioutil.ReadFile(file)
if err != nil {
return nil, err
}
var block transactions.Block
err = json.Unmarshal(data, &block)
if err != nil {
return nil, err
}
return &block, nil
}
func (s *BlockStore) ListBlocks() ([]*transactions.Block, error) {
files, err := ioutil.ReadDir(s.Path)
if err != nil {
return nil, err
}
var blocks []*transactions.Block
for _, file := range files {
if file.IsDir() || filepath.Ext(file.Name()) != ".json" {
continue
}
heightStr := file.Name()[6:9]
height, err := strconv.Atoi(heightStr)
if err != nil {
continue
}
block, err := s.LoadBlock(height)
if err == nil {
blocks = append(blocks, block)
}
}
sort.Slice(blocks, func(i, j int) bool {
return blocks[i].Index < blocks[j].Index
})
return blocks, nil
}
func (s *BlockStore) GetLatestBlock() (*transactions.Block, error) {
blocks, err := s.ListBlocks()
if err != nil || len(blocks) == 0 {
return nil, fmt.Errorf("nenhum bloco disponível")
}
return blocks[len(blocks)-1], nil
}
func (s *BlockStore) GetBlockByIndex(index int) (*transactions.Block, error) {
return s.LoadBlock(index)
}
func (s *BlockStore) LoadAll() ([]*transactions.Block, error) {
return s.ListBlocks()
}

68
internal/sync/height.go Normal file
View File

@ -0,0 +1,68 @@
package sync
import (
"encoding/json"
"fmt"
"io"
"dejo_node/internal/transactions"
network "github.com/libp2p/go-libp2p/core/network"
)
type SyncMessage struct {
Type string `json:"type"`
Height int64 `json:"height,omitempty"`
Block *transactions.Block `json:"block,omitempty"`
}
func (s *SyncManager) handleStream(stream network.Stream) {
fmt.Println("📥 [SYNC] Conexão de:", stream.Conn().RemotePeer())
defer stream.Close()
decoder := json.NewDecoder(stream)
encoder := json.NewEncoder(stream)
for {
var msg SyncMessage
if err := decoder.Decode(&msg); err != nil {
if err == io.EOF {
return
}
fmt.Println("Erro ao decodificar mensagem de sync:", err)
return
}
switch msg.Type {
case "RequestHeight":
latest, err := s.BlockStore.GetLatestBlock()
if err != nil {
fmt.Println("Erro ao obter bloco mais recente:", err)
return
}
height := int64(latest.Index)
resp := SyncMessage{
Type: "ResponseHeight",
Height: height,
}
_ = encoder.Encode(resp)
fmt.Println("↩️ Respondendo altura:", height)
case "RequestBlock":
blk, err := s.BlockStore.GetBlockByIndex(int(msg.Height))
if err != nil {
fmt.Println("❌ Erro ao buscar bloco:", err)
return
}
resp := SyncMessage{
Type: "ResponseBlock",
Block: blk,
}
_ = encoder.Encode(resp)
fmt.Printf("📤 Enviando bloco altura %d\n", msg.Height)
default:
fmt.Println("Mensagem de sync desconhecida:", msg.Type)
}
}
}

33
internal/sync/sync.go Normal file
View File

@ -0,0 +1,33 @@
package sync
import (
"context"
"fmt"
"dejo_node/internal/storage"
host "github.com/libp2p/go-libp2p/core/host"
)
const SyncProtocolID = "/dejo/sync/1.0.0"
// SyncManager coordena o processo de sincronização de blocos com outros peers.
type SyncManager struct {
Ctx context.Context
Host host.Host
BlockStore *storage.BlockStore
}
// NewSyncManager cria um novo gerenciador de sincronização.
func NewSyncManager(ctx context.Context, h host.Host, bs *storage.BlockStore) *SyncManager {
manager := &SyncManager{
Ctx: ctx,
Host: h,
BlockStore: bs,
}
h.SetStreamHandler(SyncProtocolID, manager.handleStream)
fmt.Println("🔄 Handler de sync registrado para protocolo:", SyncProtocolID)
return manager
}

View File

@ -0,0 +1,48 @@
package transactions
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"time"
)
// Block representa um bloco da blockchain DEJO.
type Block struct {
Index uint64 // posição na cadeia
PrevHash string // hash do bloco anterior
Txns []*Transaction // lista de transações
Timestamp int64 // timestamp unix
Nonce uint64 // reservado para PoW/futuro
Hash string // hash do bloco
}
// CreateBlock monta um novo bloco com transações válidas.
func CreateBlock(prevHash string, txs []*Transaction, index uint64) *Block {
block := &Block{
Index: index,
PrevHash: prevHash,
Txns: txs,
Timestamp: time.Now().Unix(),
Nonce: 0, // reservado para PoW ou consenso
}
block.Hash = block.CalculateHash()
return block
}
// CalculateHash calcula o hash do bloco.
func (b *Block) CalculateHash() string {
h := sha256.New()
h.Write([]byte(fmt.Sprintf("%d:%s:%d:%d",
b.Index, b.PrevHash, b.Timestamp, b.Nonce)))
for _, tx := range b.Txns {
h.Write([]byte(tx.Hash()))
}
return hex.EncodeToString(h.Sum(nil))
}
// ComputeHash atualiza o campo Hash com base nos dados atuais do bloco.
func (b *Block) ComputeHash() {
b.Hash = b.CalculateHash()
}

View File

@ -0,0 +1,41 @@
package transactions_test
import (
"dejo_node/internal/transactions"
"testing"
)
func TestCreateBlock(t *testing.T) {
tx1 := &transactions.Transaction{
From: "A",
To: "B",
Value: 10,
Nonce: 1,
Gas: 1,
Signature: "sig1",
}
tx2 := &transactions.Transaction{
From: "B",
To: "C",
Value: 5,
Nonce: 1,
Gas: 1,
Signature: "sig2",
}
block := transactions.CreateBlock("prevhash", []*transactions.Transaction{tx1, tx2}, 2)
if block.Hash == "" {
t.Error("hash do bloco não pode ser vazio")
}
if block.Index != 2 {
t.Errorf("esperava índice 2, obteve %d", block.Index)
}
if block.PrevHash != "prevhash" {
t.Errorf("hash anterior incorreto")
}
if len(block.Txns) != 2 {
t.Errorf("esperava 2 transações no bloco, obteve %d", len(block.Txns))
}
}

View File

@ -0,0 +1,74 @@
package transactions
import (
"errors"
"sync"
)
type Mempool struct {
txs []*Transaction
mu sync.RWMutex
}
func NewMempool() *Mempool {
return &Mempool{}
}
func (m *Mempool) Add(tx *Transaction) error {
m.mu.Lock()
defer m.mu.Unlock()
if m.Has(tx.Hash()) {
return errors.New("transação já existente na mempool")
}
m.txs = append(m.txs, tx)
return nil
}
func (m *Mempool) Remove(hash string) {
m.mu.Lock()
defer m.mu.Unlock()
for i, tx := range m.txs {
if tx.Hash() == hash {
m.txs = append(m.txs[:i], m.txs[i+1:]...)
return
}
}
}
func (m *Mempool) All() []*Transaction {
m.mu.RLock()
defer m.mu.RUnlock()
return append([]*Transaction(nil), m.txs...)
}
func (m *Mempool) GetByHash(hash string) *Transaction {
m.mu.RLock()
defer m.mu.RUnlock()
for _, tx := range m.txs {
if tx.Hash() == hash {
return tx
}
}
return nil
}
func (m *Mempool) Pending() []*Transaction {
return m.All()
}
func (m *Mempool) Clear() {
m.mu.Lock()
defer m.mu.Unlock()
m.txs = []*Transaction{}
}
func (m *Mempool) Has(hash string) bool {
m.mu.RLock()
defer m.mu.RUnlock()
for _, tx := range m.txs {
if tx.Hash() == hash {
return true
}
}
return false
}

View File

@ -0,0 +1,54 @@
package transactions_test
import (
"dejo_node/internal/transactions"
"testing"
)
func TestMempool_AddAndGet(t *testing.T) {
pool := transactions.NewMempool()
tx := &transactions.Transaction{
From: "0xabc",
To: "0xdef",
Nonce: 1,
Value: 10,
Gas: 1,
Signature: "sig",
}
err := pool.Add(tx)
if err != nil {
t.Fatalf("erro ao adicionar transação: %v", err)
}
txs := pool.All()
if len(txs) != 1 {
t.Errorf("esperava 1 transação, obteve %d", len(txs))
}
if !pool.Has(tx.Hash()) {
t.Error("transação deveria existir na mempool")
}
}
func TestMempool_Duplicates(t *testing.T) {
pool := transactions.NewMempool()
tx := &transactions.Transaction{From: "0xaaa", Nonce: 1}
_ = pool.Add(tx)
err := pool.Add(tx)
if err == nil {
t.Error("esperava erro de transação duplicada")
}
}
func TestMempool_Remove(t *testing.T) {
pool := transactions.NewMempool()
tx := &transactions.Transaction{From: "0xaaa", Nonce: 2}
_ = pool.Add(tx)
pool.Remove(tx.Hash())
if pool.Has(tx.Hash()) {
t.Error("transação ainda existe após remoção")
}
}

View File

@ -0,0 +1,33 @@
package transactions
import (
"sync"
)
// SeenTracker registra hashes de transações já vistas (para evitar replay).
type SeenTracker struct {
mu sync.RWMutex
hashes map[string]struct{}
}
// NewSeenTracker cria um novo tracker de transações vistas
func NewSeenTracker() *SeenTracker {
return &SeenTracker{
hashes: make(map[string]struct{}),
}
}
// Seen verifica se uma transação já foi vista
func (s *SeenTracker) Seen(hash string) bool {
s.mu.RLock()
defer s.mu.RUnlock()
_, exists := s.hashes[hash]
return exists
}
// Mark marca uma transação como vista
func (s *SeenTracker) Mark(hash string) {
s.mu.Lock()
defer s.mu.Unlock()
s.hashes[hash] = struct{}{}
}

View File

@ -0,0 +1,29 @@
package transactions
import (
"crypto/sha256"
"encoding/hex"
"fmt"
)
type Transaction struct {
Type string // TRANSFER, STAKE, MINT, etc.
From string
To string
Value float64
Nonce uint64
Gas uint64
Signature string
}
// Hash gera um hash SHA256 da transação para identificação única
func (tx *Transaction) Hash() string {
data := fmt.Sprintf("%s:%s:%f:%d:%d", tx.From, tx.To, tx.Value, tx.Nonce, tx.Gas)
sum := sha256.Sum256([]byte(data))
return hex.EncodeToString(sum[:])
}
// IsZero valida se os campos obrigatórios estão presentes
func (tx *Transaction) IsZero() bool {
return tx.From == "" || tx.To == "" || tx.Value == 0
}

View File

@ -0,0 +1,39 @@
package transactions_test
import (
"dejo_node/internal/transactions"
"testing"
)
func TestTransaction_Hash(t *testing.T) {
tx := transactions.Transaction{
From: "0xabc",
To: "0xdef",
Value: 100,
Nonce: 1,
Gas: 21000,
Signature: "0xsig",
}
hash := tx.Hash()
if hash == "" {
t.Fatal("hash não pode ser vazio")
}
}
func TestTransaction_IsZero(t *testing.T) {
tx := transactions.Transaction{}
if !tx.IsZero() {
t.Error("transação vazia deveria retornar true para IsZero")
}
tx = transactions.Transaction{
From: "0xabc",
To: "0xdef",
Value: 10,
Signature: "0xsig",
}
if tx.IsZero() {
t.Error("transação válida retornou true para IsZero")
}
}

View File

@ -0,0 +1,63 @@
package transactions
import (
"crypto/ecdsa"
"crypto/rand"
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"math/big"
)
// IsValid realiza validações na transação: assinatura, saldo e nonce.
func (tx *Transaction) IsValid(pubKey *ecdsa.PublicKey, balance float64, currentNonce uint64) error {
if tx.IsZero() {
return errors.New("transação malformada: campos obrigatórios ausentes")
}
if tx.Value+float64(tx.Gas) > balance {
return fmt.Errorf("saldo insuficiente: necessário %.2f, disponível %.2f", tx.Value+float64(tx.Gas), balance)
}
if tx.Nonce != currentNonce {
return fmt.Errorf("nonce inválido: esperado %d, recebido %d", currentNonce, tx.Nonce)
}
r, s, err := parseSignature(tx.Signature)
if err != nil {
return fmt.Errorf("erro ao parsear assinatura: %v", err)
}
hash := sha256.Sum256([]byte(fmt.Sprintf("%s:%s:%f:%d:%d",
tx.From, tx.To, tx.Value, tx.Nonce, tx.Gas)))
if !ecdsa.Verify(pubKey, hash[:], r, s) {
return errors.New("assinatura inválida")
}
return nil
}
// parseSignature converte a assinatura hex (formato r||s) em *big.Int.
func parseSignature(sig string) (*big.Int, *big.Int, error) {
bytes, err := hex.DecodeString(sig)
if err != nil {
return nil, nil, err
}
if len(bytes) != 64 {
return nil, nil, errors.New("assinatura deve ter 64 bytes (r||s)")
}
r := new(big.Int).SetBytes(bytes[:32])
s := new(big.Int).SetBytes(bytes[32:])
return r, s, nil
}
// GenerateSignature é uma função auxiliar (para testes): assina tx com chave privada.
func (tx *Transaction) GenerateSignature(priv *ecdsa.PrivateKey) string {
hash := sha256.Sum256([]byte(fmt.Sprintf("%s:%s:%f:%d:%d",
tx.From, tx.To, tx.Value, tx.Nonce, tx.Gas)))
r, s, _ := ecdsa.Sign(rand.Reader, priv, hash[:])
return fmt.Sprintf("%064x%064x", r, s)
}

View File

@ -0,0 +1,48 @@
package transactions_test
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"dejo_node/internal/transactions"
"testing"
)
func TestTransaction_IsValid(t *testing.T) {
priv, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
pub := &priv.PublicKey
tx := transactions.Transaction{
From: "0xabc",
To: "0xdef",
Value: 50,
Gas: 10,
Nonce: 1,
}
tx.Signature = tx.GenerateSignature(priv)
err := tx.IsValid(pub, 100, 1)
if err != nil {
t.Errorf("esperava transação válida, mas falhou: %v", err)
}
}
func TestTransaction_IsValid_InvalidSignature(t *testing.T) {
priv, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
wrongPriv, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
pub := &priv.PublicKey
tx := transactions.Transaction{
From: "0xabc",
To: "0xdef",
Value: 50,
Gas: 10,
Nonce: 1,
}
tx.Signature = tx.GenerateSignature(wrongPriv)
err := tx.IsValid(pub, 100, 1)
if err == nil {
t.Error("esperava falha na assinatura, mas foi aceita")
}
}

20
internal/ws/events.go Normal file
View File

@ -0,0 +1,20 @@
package ws
import (
"encoding/json"
"fmt"
)
// Emit envia um evento para todos os clientes conectados
func Emit(eventType string, data any) {
msg := map[string]any{
"type": eventType,
"data": data,
}
jsonMsg, err := json.Marshal(msg)
if err != nil {
fmt.Println("Erro ao serializar evento WebSocket:", err)
return
}
broadcast <- jsonMsg
}

Some files were not shown because too many files have changed in this diff Show More