mirror of
https://github.com/luxfi/bft.git
synced 2026-10-30 17:01:54 +00:00
Reapplies the fork's whole delta on top of the merge, as one reviewable diff rather than 43 conflict resolutions. Nothing here changes consensus behaviour. - module github.com/luxfi/bft, Go 1.26.4; root package simplex -> bft, which would otherwise collide with the simplex/ subpackage under this module name. - go.uber.org/zap -> github.com/luxfi/log, so common.Logger takes ...log.Field. zap.Error maps to log.Err: log.Error is luxfi/log's package-level logging function, not a field constructor, and the compiler is the only thing that catches that. Needs luxfi/log v1.6.1 for the Stringers constructor. - avalanchego/ -> host/. The package holds NodeID, VMBlock, Bitmask and size limits — the host node's types, vendored to avoid importing a whole node — so it is named for what it holds. Renaming it luxd/ would have swapped one brand for another and said just as little. - testutil/logger.go reimplemented without zapcore. Upstream layers a keyword-filtering Core, a debug-swallowing Encoder and a Tee because that is zap's extension model; luxfi/log has no such seam, so every policy is applied in one emit. That also fixes two defects the layering caused: SilenceExceptKeywords rebuilt the logger and discarded an installed Intercept hook, and the two sinks read different env vars (LOG_LEVEL vs log_level) so they disagreed about suppressing debug. Hooks run outside the lock, so a hook that logs fails the test instead of deadlocking it. Fatal panics rather than exiting, because exiting takes the suite down with no attribution. - simplex/monitor_test.go keeps a local logger: testutil imports simplex, so an internal test cannot import testutil back without closing a cycle. - README keeps the name Simplex throughout — it is the protocol's own name and the subject of reference [1]. Only the deployment target changed. The HyperSDK sentence still says Avalanche because that sentence is true. - Drops the fork's Makefile: it duplicated unit-tests.sh and fuzz.sh, and ran the suite with -timeout 30s when simplex alone needs over 100s. Co-authored-by: Hanzo Dev <[email protected]>
202 lines
5.1 KiB
Go
202 lines
5.1 KiB
Go
// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package wal
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"fmt"
|
|
|
|
"github.com/luxfi/bft/common"
|
|
)
|
|
|
|
const (
|
|
defaultMaxWALSize = 100 * 1024 * 1024 // 100 MB
|
|
)
|
|
|
|
// Creator creates a DeletableWAL. Returns an error upon failure.
|
|
type Creator func() (DeletableWAL, error)
|
|
|
|
// Reader reads the retention term of a WAL payload.
|
|
// Returns an error upon failure.
|
|
type Reader interface {
|
|
RetentionTerm([]byte) (uint64, error)
|
|
}
|
|
|
|
// TruncateableWAL is a DeletableWAL that can be recentlyTruncated to a given retention term.
|
|
// It removes all entries below the given retention term.
|
|
type TruncateableWAL interface {
|
|
DeletableWAL
|
|
Truncate(retentionTerm uint64) error
|
|
}
|
|
|
|
// DeletableWAL is a WAL that can be deleted.
|
|
type DeletableWAL interface {
|
|
common.WriteAheadLog
|
|
// Delete deletes the WAL file and after it is called,
|
|
// it should no longer be used.
|
|
Delete() error
|
|
}
|
|
|
|
type garbageCollectedWAL struct {
|
|
retentionTerm uint64
|
|
size int
|
|
DeletableWAL
|
|
}
|
|
|
|
type GarbageCollectedWAL struct {
|
|
wals []garbageCollectedWAL
|
|
createWAL Creator
|
|
maxWalSize int
|
|
reader Reader
|
|
}
|
|
|
|
// NewGarbageCollectedWAL creates a new GarbageCollectedWAL.
|
|
// It takes in a list of existing WALs to load, a Creator to create new WALs,
|
|
// a Reader to read the retention term of entries, and a maximum WAL size.
|
|
// A new WAL is created when the given maximum WAL size of a WAL is reached.
|
|
func NewGarbageCollectedWAL(WALs []DeletableWAL, creator Creator, reader Reader, maxWALSize int) (*GarbageCollectedWAL, error) {
|
|
wals := make([]garbageCollectedWAL, 0, len(WALs))
|
|
for _, wal := range WALs {
|
|
wal, err := loadGarbageCollectedWAL(wal, reader)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
wals = append(wals, wal)
|
|
}
|
|
|
|
if maxWALSize == 0 {
|
|
maxWALSize = defaultMaxWALSize
|
|
}
|
|
|
|
return &GarbageCollectedWAL{
|
|
maxWalSize: maxWALSize,
|
|
reader: reader,
|
|
createWAL: creator,
|
|
wals: wals,
|
|
}, nil
|
|
}
|
|
|
|
func loadGarbageCollectedWAL(wal DeletableWAL, reader Reader) (garbageCollectedWAL, error) {
|
|
entries, err := wal.ReadAll()
|
|
if err != nil {
|
|
return garbageCollectedWAL{}, fmt.Errorf("error reading entries from WAL: %v", err)
|
|
}
|
|
|
|
var highestRetentionTerm uint64
|
|
var size int
|
|
|
|
for _, entry := range entries {
|
|
size += len(entry)
|
|
rt, err := reader.RetentionTerm(entry)
|
|
if err != nil {
|
|
data := base64.StdEncoding.EncodeToString(entry)
|
|
return garbageCollectedWAL{}, fmt.Errorf("error reading retention term of entry %s: %v", data, err)
|
|
}
|
|
if rt > highestRetentionTerm {
|
|
highestRetentionTerm = rt
|
|
}
|
|
}
|
|
return garbageCollectedWAL{
|
|
retentionTerm: highestRetentionTerm,
|
|
DeletableWAL: wal,
|
|
size: size,
|
|
}, nil
|
|
}
|
|
|
|
func (gcw *GarbageCollectedWAL) Append(payload []byte) error {
|
|
rt, err := gcw.reader.RetentionTerm(payload)
|
|
if err != nil {
|
|
data := base64.StdEncoding.EncodeToString(payload)
|
|
return fmt.Errorf("error reading retention term of entry %s: %v", data, err)
|
|
}
|
|
|
|
if gcw.shouldWeCreateNewWAL(payload) {
|
|
// Close the last WAL in use because we won't be needing it anymore
|
|
if len(gcw.wals) > 0 {
|
|
if err := gcw.wals[len(gcw.wals)-1].Close(); err != nil {
|
|
return fmt.Errorf("error closing last WAL: %v", err)
|
|
}
|
|
}
|
|
newWAL, err := gcw.createWAL()
|
|
if err != nil {
|
|
return fmt.Errorf("error creating new WAL: %v", err)
|
|
}
|
|
gcw.wals = append(gcw.wals, garbageCollectedWAL{
|
|
DeletableWAL: newWAL,
|
|
retentionTerm: rt,
|
|
size: len(payload),
|
|
})
|
|
} else {
|
|
// We're still using the last WAL, so we need to update its size
|
|
gcw.wals[len(gcw.wals)-1].size += len(payload)
|
|
}
|
|
|
|
last := len(gcw.wals) - 1
|
|
if rt > gcw.wals[last].retentionTerm {
|
|
gcw.wals[last].retentionTerm = rt
|
|
}
|
|
|
|
return gcw.wals[last].Append(payload)
|
|
}
|
|
|
|
func (gcw *GarbageCollectedWAL) shouldWeCreateNewWAL(payload []byte) bool {
|
|
var shouldCreateNewWAL bool
|
|
|
|
if len(gcw.wals) == 0 {
|
|
shouldCreateNewWAL = true
|
|
} else {
|
|
lastWALSize := gcw.wals[len(gcw.wals)-1].size
|
|
shouldCreateNewWAL = lastWALSize+len(payload) > gcw.maxWalSize
|
|
}
|
|
return shouldCreateNewWAL
|
|
}
|
|
|
|
func (gcw *GarbageCollectedWAL) ReadAll() ([][]byte, error) {
|
|
var allEntries [][]byte
|
|
|
|
for i, wal := range gcw.wals {
|
|
entries, err := wal.ReadAll()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error reading entries from WAL %d: %v", i, err)
|
|
}
|
|
if err := wal.Close(); err != nil {
|
|
return nil, fmt.Errorf("error closing WAL %d: %v", i, err)
|
|
}
|
|
|
|
allEntries = append(allEntries, entries...)
|
|
}
|
|
|
|
return allEntries, nil
|
|
}
|
|
|
|
func (gcw *GarbageCollectedWAL) GarbageCollect(retentionTerm uint64) error {
|
|
newWALs := make([]garbageCollectedWAL, 0, len(gcw.wals))
|
|
|
|
for i, wal := range gcw.wals {
|
|
if wal.retentionTerm < retentionTerm {
|
|
// This WAL is below the retention term, we can delete it
|
|
if err := wal.Delete(); err != nil {
|
|
return fmt.Errorf("error deleting WAL %d: %v", i, err)
|
|
}
|
|
continue
|
|
}
|
|
// Else, this WAL is still needed
|
|
newWALs = append(newWALs, wal)
|
|
}
|
|
|
|
gcw.wals = newWALs
|
|
|
|
return nil
|
|
}
|
|
|
|
func (gcw *GarbageCollectedWAL) Close() error {
|
|
for i, wal := range gcw.wals {
|
|
if err := wal.Close(); err != nil {
|
|
return fmt.Errorf("error closing WAL %d: %v", i, err)
|
|
}
|
|
}
|
|
gcw.wals = nil
|
|
return nil
|
|
}
|