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
This commit is contained in:
139
blockchain/block.go
Normal file
139
blockchain/block.go
Normal file
@@ -0,0 +1,139 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user