Files
zeekay 7cf87b230b node-unified is the node
The module already called itself github.com/luxfi/node while containing no node
in Go: cmd/luxd was twenty-one lines that printed the size of a few types, and
the Go column of the matrix measured a VM plugin. A repo that fetches the thing
it claims to be has not replaced it, so the Go sources move in — 1515 files,
the daemon and everything under it — and the build reaches outside this module
for nothing.

Rust and C++ sit beside it unchanged and are still measured against it by the
differential. That is the point of one repo: the reference implementation and
the two ports are the same checkout, so parity is a thing you can run rather
than a thing you assert.

Verified: builds, and the closure carries no lux-private — checked at build time
rather than believed. The daemon starts twelve chains on a local network
(P X C Q Z A B G K M F, plus D through its plugin) and reports healthy.
2026-09-07 16:15:25 -07:00

386 lines
9.0 KiB
Go

// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package da
import (
"context"
"errors"
"sync"
"time"
"github.com/luxfi/database"
"github.com/luxfi/ids"
)
var (
blobPrefix = []byte("blob:")
certPrefix = []byte("cert:")
// ErrBlobNotFound indicates blob was not found
ErrBlobNotFound = errors.New("blob not found")
// ErrCertNotFound indicates certificate was not found
ErrCertNotFound = errors.New("DA certificate not found")
)
// Store manages DA blob storage and retrieval
type Store struct {
db database.Database
blobs map[ids.ID]*DABlob
certs map[ids.ID]*DACert
mu sync.RWMutex
config *StoreConfig
}
// StoreConfig configures the DA store
type StoreConfig struct {
MaxBlobCache int `json:"maxBlobCache"` // Maximum blobs to cache in memory
RetentionPeriod int64 `json:"retentionPeriod"` // Blob retention in seconds
EnableErasure bool `json:"enableErasure"` // Enable erasure coding
ErasureDataRatio int `json:"erasureDataRatio"` // Data to parity ratio
}
// DefaultStoreConfig returns default store configuration
func DefaultStoreConfig() *StoreConfig {
return &StoreConfig{
MaxBlobCache: 1000,
RetentionPeriod: 7 * 24 * 3600, // 7 days
EnableErasure: true,
ErasureDataRatio: 2,
}
}
// NewStore creates a new DA store
func NewStore(db database.Database, config *StoreConfig) *Store {
if config == nil {
config = DefaultStoreConfig()
}
return &Store{
db: db,
blobs: make(map[ids.ID]*DABlob),
certs: make(map[ids.ID]*DACert),
config: config,
}
}
// StoreBlob stores a DA blob (ZAP-encoded).
func (s *Store) StoreBlob(ctx context.Context, blob *DABlob) error {
s.mu.Lock()
defer s.mu.Unlock()
// Store in memory cache
s.blobs[blob.ID] = blob
// Persist to database
blobBytes := encodeDABlob(blob)
key := append(blobPrefix, blob.ID[:]...)
return s.db.Put(key, blobBytes)
}
// GetBlob retrieves a DA blob by ID
func (s *Store) GetBlob(ctx context.Context, blobID ids.ID) (*DABlob, error) {
s.mu.RLock()
if blob, ok := s.blobs[blobID]; ok {
s.mu.RUnlock()
return blob, nil
}
s.mu.RUnlock()
// Try database
key := append(blobPrefix, blobID[:]...)
blobBytes, err := s.db.Get(key)
if err != nil {
if err == database.ErrNotFound {
return nil, ErrBlobNotFound
}
return nil, err
}
blob, err := decodeDABlob(blobBytes)
if err != nil {
return nil, err
}
// Cache for future access
s.mu.Lock()
s.blobs[blobID] = blob
s.mu.Unlock()
return blob, nil
}
// DeleteBlob deletes a DA blob
func (s *Store) DeleteBlob(ctx context.Context, blobID ids.ID) error {
s.mu.Lock()
delete(s.blobs, blobID)
s.mu.Unlock()
key := append(blobPrefix, blobID[:]...)
return s.db.Delete(key)
}
// StoreCert stores a DA certificate (ZAP-encoded).
func (s *Store) StoreCert(ctx context.Context, cert *DACert) error {
s.mu.Lock()
defer s.mu.Unlock()
s.certs[cert.Commitment.BlobID] = cert
certBytes := encodeDACert(cert)
key := append(certPrefix, cert.Commitment.BlobID[:]...)
return s.db.Put(key, certBytes)
}
// GetCert retrieves a DA certificate by blob ID
func (s *Store) GetCert(ctx context.Context, blobID ids.ID) (*DACert, error) {
s.mu.RLock()
if cert, ok := s.certs[blobID]; ok {
s.mu.RUnlock()
return cert, nil
}
s.mu.RUnlock()
key := append(certPrefix, blobID[:]...)
certBytes, err := s.db.Get(key)
if err != nil {
if err == database.ErrNotFound {
return nil, ErrCertNotFound
}
return nil, err
}
cert, err := decodeDACert(certBytes)
if err != nil {
return nil, err
}
s.mu.Lock()
s.certs[blobID] = cert
s.mu.Unlock()
return cert, nil
}
// GetChunks retrieves specific chunks from a blob
func (s *Store) GetChunks(ctx context.Context, blobID ids.ID, indices []uint32) ([]*Chunk, error) {
blob, err := s.GetBlob(ctx, blobID)
if err != nil {
return nil, err
}
chunks := make([]*Chunk, 0, len(indices))
for _, idx := range indices {
if idx < blob.ChunkCount {
chunks = append(chunks, blob.Chunks[idx])
}
}
return chunks, nil
}
// PruneExpired removes expired blobs
func (s *Store) PruneExpired(ctx context.Context) (int, error) {
s.mu.Lock()
defer s.mu.Unlock()
cutoff := time.Now().Add(-time.Duration(s.config.RetentionPeriod) * time.Second)
pruned := 0
for id, blob := range s.blobs {
if blob.Timestamp.Before(cutoff) {
delete(s.blobs, id)
delete(s.certs, id)
key := append(blobPrefix, id[:]...)
s.db.Delete(key)
certKey := append(certPrefix, id[:]...)
s.db.Delete(certKey)
pruned++
}
}
return pruned, nil
}
// Stats returns store statistics
func (s *Store) Stats() map[string]interface{} {
s.mu.RLock()
defer s.mu.RUnlock()
return map[string]interface{}{
"blobCount": len(s.blobs),
"certCount": len(s.certs),
"maxBlobCache": s.config.MaxBlobCache,
}
}
// ======== DA Validator ========
// Validator handles DA validation and certification
type Validator struct {
store *Store
sampler *Sampler
config *ValidatorConfig
}
// ValidatorConfig configures the DA validator
type ValidatorConfig struct {
MinSignatures int `json:"minSignatures"` // Minimum validator signatures for cert
SamplingEnabled bool `json:"samplingEnabled"` // Enable light client sampling
ConfidenceTarget float64 `json:"confidenceTarget"` // Target confidence level
}
// DefaultValidatorConfig returns default validator configuration
func DefaultValidatorConfig() *ValidatorConfig {
return &ValidatorConfig{
MinSignatures: 2, // 2/3 threshold typical
SamplingEnabled: true,
ConfidenceTarget: 0.99,
}
}
// NewValidator creates a new DA validator
func NewValidator(store *Store, config *ValidatorConfig) *Validator {
if config == nil {
config = DefaultValidatorConfig()
}
return &Validator{
store: store,
sampler: NewSampler(DefaultSamplingConfig()),
config: config,
}
}
// ValidateBlob validates a DA blob and returns commitment
func (v *Validator) ValidateBlob(ctx context.Context, blob *DABlob) (*DACommitment, error) {
// Verify blob size
if len(blob.Data) > MaxBlobSize {
return nil, ErrBlobTooLarge
}
// Verify chunks match data
reconstructed := make([]byte, 0, len(blob.Chunks)*int(blob.ChunkSize))
for _, chunk := range blob.Chunks {
reconstructed = append(reconstructed, chunk.Data...)
}
// Trim padding
if len(reconstructed) > len(blob.Data) {
reconstructed = reconstructed[:len(blob.Data)]
}
if len(reconstructed) != len(blob.Data) {
return nil, errors.New("chunk data does not match blob data")
}
for i := range blob.Data {
if blob.Data[i] != reconstructed[i] {
return nil, errors.New("chunk data mismatch")
}
}
// Verify commitment
for _, chunk := range blob.Chunks {
if !blob.VerifyChunk(chunk) {
return nil, ErrInvalidCommitment
}
}
// Create commitment
return blob.CreateCommitment(), nil
}
// CertifyAvailability certifies data is available
func (v *Validator) CertifyAvailability(ctx context.Context, commitment *DACommitment, signature []byte) (*DACert, error) {
// Get existing cert or create new one
cert, err := v.store.GetCert(ctx, commitment.BlobID)
if err == ErrCertNotFound {
cert = &DACert{
Commitment: commitment,
Signatures: make([][]byte, 0),
SignerBitmap: make([]byte, 32), // Support up to 256 validators
Threshold: uint32(v.config.MinSignatures),
Timestamp: time.Now().Unix(),
}
} else if err != nil {
return nil, err
}
// Add signature
cert.Signatures = append(cert.Signatures, signature)
// Store updated cert
if err := v.store.StoreCert(ctx, cert); err != nil {
return nil, err
}
return cert, nil
}
// VerifyCert verifies a DA certificate
func (v *Validator) VerifyCert(ctx context.Context, cert *DACert) (bool, error) {
// Verify minimum signatures
if len(cert.Signatures) < int(cert.Threshold) {
return false, nil
}
// Verify each signature (simplified - would verify crypto)
for _, sig := range cert.Signatures {
if len(sig) == 0 {
return false, nil
}
}
return true, nil
}
// SampleAndVerify performs DA sampling and verification
func (v *Validator) SampleAndVerify(ctx context.Context, blobID ids.ID, seed []byte) (*SamplingResult, error) {
blob, err := v.store.GetBlob(ctx, blobID)
if err != nil {
return nil, err
}
// Generate sample indices
indices := v.sampler.GenerateSampleIndices(blobID, blob.ChunkCount, seed)
// Get samples
samples := make([]*Sample, 0, len(indices))
for _, idx := range indices {
chunk, err := blob.GetChunk(idx)
if err != nil {
continue
}
samples = append(samples, &Sample{
ChunkIndex: idx,
Data: chunk.Data,
Proof: chunk.Proof,
})
}
// Verify samples
return v.sampler.VerifySamples(blob, samples), nil
}
// CalculateConfidence calculates confidence level from sample count
func (v *Validator) CalculateConfidence(sampleCount int, successRate float64) float64 {
// Simplified confidence calculation
// Real implementation would use statistical formulas
if sampleCount < MinSampleCount {
return 0
}
baseConfidence := successRate
sampleFactor := 1 - (float64(MinSampleCount) / float64(sampleCount*2))
return baseConfidence * sampleFactor
}