Files
dchain/blockchain/block.go
vsecoder 7e7393e4f8 chore: initial commit for v0.0.1
DChain single-node blockchain + React Native messenger client.

Core:
- PBFT consensus with multi-sig validator admission + equivocation slashing
- BadgerDB + schema migration scaffold (CurrentSchemaVersion=0)
- libp2p gossipsub (tx/v1, blocks/v1, relay/v1, version/v1)
- Native Go contracts (username_registry) alongside WASM (wazero)
- WebSocket gateway with topic-based fanout + Ed25519-nonce auth
- Relay mailbox with NaCl envelope encryption (X25519 + Ed25519)
- Prometheus /metrics, per-IP rate limit, body-size cap

Deployment:
- Single-node compose (deploy/single/) with Caddy TLS + optional Prometheus
- 3-node dev compose (docker-compose.yml) with mocked internet topology
- 3-validator prod compose (deploy/prod/) for federation
- Auto-update from Gitea via /api/update-check + systemd timer
- Build-time version injection (ldflags → node --version)
- UI / Swagger toggle flags (DCHAIN_DISABLE_UI, DCHAIN_DISABLE_SWAGGER)

Client (client-app/):
- Expo / React Native / NativeWind
- E2E NaCl encryption, typing indicator, contact requests
- Auto-discovery of canonical contracts, chain_id aware, WS reconnect on node switch

Documentation:
- README.md, CHANGELOG.md, CONTEXT.md
- deploy/single/README.md with 6 operator scenarios
- deploy/UPDATE_STRATEGY.md with 4-layer forward-compat design
- docs/contracts/*.md per contract
2026-04-17 14:16:44 +03:00

140 lines
3.7 KiB
Go

package blockchain
import (
"bytes"
"crypto/ed25519"
"crypto/sha256"
"encoding/binary"
"encoding/hex"
"encoding/json"
"errors"
"time"
)
// Block is the fundamental unit of the chain.
type Block struct {
Index uint64 `json:"index"`
Timestamp time.Time `json:"timestamp"`
Transactions []*Transaction `json:"transactions"`
PrevHash []byte `json:"prev_hash"`
Hash []byte `json:"hash"` // SHA-256 over canonical fields
ValidatorSig []byte `json:"validator_sig"` // Ed25519 sig over Hash
Validator string `json:"validator"` // hex pub key of signing validator
// TotalFees collected in this block (credited to Validator)
TotalFees uint64 `json:"total_fees"`
}
// canonicalBytes returns a deterministic byte slice for hashing.
// Order: index | timestamp | prev_hash | tx_hashes | total_fees | validator
func (b *Block) canonicalBytes() []byte {
var buf bytes.Buffer
// 8-byte big-endian index
idxBuf := make([]byte, 8)
binary.BigEndian.PutUint64(idxBuf, b.Index)
buf.Write(idxBuf)
// 8-byte unix nano timestamp
tsBuf := make([]byte, 8)
binary.BigEndian.PutUint64(tsBuf, uint64(b.Timestamp.UnixNano()))
buf.Write(tsBuf)
buf.Write(b.PrevHash)
// Hash each transaction and include its hash
for _, tx := range b.Transactions {
h := txHash(tx)
buf.Write(h)
}
// 8-byte fees
feesBuf := make([]byte, 8)
binary.BigEndian.PutUint64(feesBuf, b.TotalFees)
buf.Write(feesBuf)
buf.WriteString(b.Validator)
return buf.Bytes()
}
// txHash returns SHA-256 of the canonical transaction bytes.
func txHash(tx *Transaction) []byte {
data, _ := json.Marshal(tx)
h := sha256.Sum256(data)
return h[:]
}
// ComputeHash fills b.Hash from the canonical bytes.
func (b *Block) ComputeHash() {
sum := sha256.Sum256(b.canonicalBytes())
b.Hash = sum[:]
}
// Sign signs b.Hash with the given Ed25519 private key and stores the signature.
func (b *Block) Sign(privKey ed25519.PrivateKey) {
b.ValidatorSig = ed25519.Sign(privKey, b.Hash)
}
// Validate checks the block's structural integrity:
// 1. Hash matches canonical bytes
// 2. ValidatorSig is a valid Ed25519 signature over Hash
// 3. PrevHash is provided (except genesis)
func (b *Block) Validate(prevHash []byte) error {
// Recompute and compare hash
sum := sha256.Sum256(b.canonicalBytes())
if !bytes.Equal(sum[:], b.Hash) {
return errors.New("block hash mismatch")
}
// Verify validator signature
pubKeyBytes, err := hex.DecodeString(b.Validator)
if err != nil {
return errors.New("invalid validator pub key hex")
}
if !ed25519.Verify(ed25519.PublicKey(pubKeyBytes), b.Hash, b.ValidatorSig) {
return errors.New("invalid validator signature")
}
// Check chain linkage (skip for genesis)
if b.Index > 0 {
if !bytes.Equal(b.PrevHash, prevHash) {
return errors.New("prev_hash mismatch")
}
}
// Validate each transaction's fee minimum
var totalFees uint64
for _, tx := range b.Transactions {
if tx.Fee < MinFee {
return errors.New("transaction fee below minimum")
}
totalFees += tx.Fee
}
if totalFees != b.TotalFees {
return errors.New("total_fees mismatch")
}
return nil
}
// GenesisBlock creates the first block with no transactions.
// It is signed by the bootstrap validator.
func GenesisBlock(validatorPubHex string, privKey ed25519.PrivateKey) *Block {
b := &Block{
Index: 0,
Timestamp: time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC),
Transactions: []*Transaction{},
PrevHash: bytes.Repeat([]byte{0}, 32),
Validator: validatorPubHex,
TotalFees: 0,
}
b.ComputeHash()
b.Sign(privKey)
return b
}
// HashHex returns the block hash as a hex string.
func (b *Block) HashHex() string {
return hex.EncodeToString(b.Hash)
}