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
140 lines
3.7 KiB
Go
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)
|
|
}
|