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

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