mirror of
https://github.com/luxfi/node.git
synced 2026-10-30 15:24:05 +00:00
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.
247 lines
6.2 KiB
Go
247 lines
6.2 KiB
Go
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package compression
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"runtime"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
_ "embed"
|
|
|
|
"github.com/luxfi/node/utils"
|
|
"github.com/luxfi/units"
|
|
)
|
|
|
|
const maxMessageSize = int64(2 * units.MiB) // Max message size. Can't import due to cycle.
|
|
|
|
var (
|
|
newCompressorFuncs = map[Type]func(maxSize int64) (Compressor, error){
|
|
TypeNone: func(int64) (Compressor, error) { //nolint:unparam // an error is needed to be returned to compile
|
|
return NewNoCompressor(), nil
|
|
},
|
|
TypeZstd: NewZstdCompressor,
|
|
}
|
|
|
|
//go:embed zstd_zip_bomb.bin
|
|
zstdZipBomb []byte
|
|
|
|
zipBombs = map[Type][]byte{
|
|
TypeZstd: zstdZipBomb,
|
|
}
|
|
)
|
|
|
|
func TestDecompressZipBombs(t *testing.T) {
|
|
for compressionType, zipBomb := range zipBombs {
|
|
// Make sure that the hardcoded zip bomb would be a valid message.
|
|
require.Less(t, len(zipBomb), int(maxMessageSize))
|
|
|
|
newCompressorFunc := newCompressorFuncs[compressionType]
|
|
|
|
t.Run(compressionType.String(), func(t *testing.T) {
|
|
require := require.New(t)
|
|
|
|
compressor, err := newCompressorFunc(maxMessageSize)
|
|
require.NoError(err)
|
|
|
|
var (
|
|
beforeDecompressionStats runtime.MemStats
|
|
afterDecompressionStats runtime.MemStats
|
|
)
|
|
runtime.ReadMemStats(&beforeDecompressionStats)
|
|
_, err = compressor.Decompress(zipBomb)
|
|
runtime.ReadMemStats(&afterDecompressionStats)
|
|
|
|
require.ErrorIs(err, ErrDecompressedMsgTooLarge)
|
|
|
|
// Make sure that we didn't allocate significantly more memory than
|
|
// the max message size.
|
|
bytesAllocatedDuringDecompression := afterDecompressionStats.TotalAlloc - beforeDecompressionStats.TotalAlloc
|
|
require.Less(bytesAllocatedDuringDecompression, uint64(10*maxMessageSize))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCompressDecompress(t *testing.T) {
|
|
for compressionType, newCompressorFunc := range newCompressorFuncs {
|
|
t.Run(compressionType.String(), func(t *testing.T) {
|
|
require := require.New(t)
|
|
|
|
data := utils.RandomBytes(4096)
|
|
data2 := utils.RandomBytes(4096)
|
|
|
|
compressor, err := newCompressorFunc(maxMessageSize)
|
|
require.NoError(err)
|
|
|
|
dataCompressed, err := compressor.Compress(data)
|
|
require.NoError(err)
|
|
|
|
data2Compressed, err := compressor.Compress(data2)
|
|
require.NoError(err)
|
|
|
|
dataDecompressed, err := compressor.Decompress(dataCompressed)
|
|
require.NoError(err)
|
|
require.Equal(data, dataDecompressed)
|
|
|
|
data2Decompressed, err := compressor.Decompress(data2Compressed)
|
|
require.NoError(err)
|
|
require.Equal(data2, data2Decompressed)
|
|
|
|
dataDecompressed, err = compressor.Decompress(dataCompressed)
|
|
require.NoError(err)
|
|
require.Equal(data, dataDecompressed)
|
|
|
|
maxMessage := utils.RandomBytes(int(maxMessageSize))
|
|
maxMessageCompressed, err := compressor.Compress(maxMessage)
|
|
require.NoError(err)
|
|
|
|
maxMessageDecompressed, err := compressor.Decompress(maxMessageCompressed)
|
|
require.NoError(err)
|
|
|
|
require.Equal(maxMessage, maxMessageDecompressed)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSizeLimiting(t *testing.T) {
|
|
for compressionType, compressorFunc := range newCompressorFuncs {
|
|
if compressionType == TypeNone {
|
|
continue
|
|
}
|
|
t.Run(compressionType.String(), func(t *testing.T) {
|
|
require := require.New(t)
|
|
|
|
compressor, err := compressorFunc(maxMessageSize)
|
|
require.NoError(err)
|
|
|
|
data := make([]byte, int(maxMessageSize)+1)
|
|
_, err = compressor.Compress(data) // should be too large
|
|
require.ErrorIs(err, ErrMsgTooLarge)
|
|
|
|
compressor2, err := compressorFunc(2 * maxMessageSize)
|
|
require.NoError(err)
|
|
|
|
dataCompressed, err := compressor2.Compress(data)
|
|
require.NoError(err)
|
|
|
|
_, err = compressor.Decompress(dataCompressed) // should be too large
|
|
require.ErrorIs(err, ErrDecompressedMsgTooLarge)
|
|
})
|
|
}
|
|
}
|
|
|
|
// Attempts to create a compressor with math.MaxInt64
|
|
// which leads to undefined decompress behavior due to integer overflow
|
|
// in limit reader creation.
|
|
func TestNewCompressorWithInvalidLimit(t *testing.T) {
|
|
for compressionType, compressorFunc := range newCompressorFuncs {
|
|
if compressionType == TypeNone {
|
|
continue
|
|
}
|
|
t.Run(compressionType.String(), func(t *testing.T) {
|
|
_, err := compressorFunc(math.MaxInt64)
|
|
require.ErrorIs(t, err, ErrInvalidMaxSizeCompressor)
|
|
})
|
|
}
|
|
}
|
|
|
|
func FuzzZstdCompressor(f *testing.F) {
|
|
fuzzHelper(f, TypeZstd)
|
|
}
|
|
|
|
func fuzzHelper(f *testing.F, compressionType Type) {
|
|
var (
|
|
compressor Compressor
|
|
err error
|
|
)
|
|
switch compressionType {
|
|
case TypeZstd:
|
|
compressor, err = NewZstdCompressor(maxMessageSize)
|
|
require.NoError(f, err)
|
|
default:
|
|
require.FailNow(f, "Unknown compression type")
|
|
}
|
|
|
|
f.Fuzz(func(t *testing.T, data []byte) {
|
|
require := require.New(t)
|
|
|
|
if len(data) > int(maxMessageSize) {
|
|
_, err := compressor.Compress(data)
|
|
require.ErrorIs(err, ErrMsgTooLarge)
|
|
return
|
|
}
|
|
|
|
compressed, err := compressor.Compress(data)
|
|
require.NoError(err)
|
|
|
|
decompressed, err := compressor.Decompress(compressed)
|
|
require.NoError(err)
|
|
|
|
require.Equal(data, decompressed)
|
|
})
|
|
}
|
|
|
|
func BenchmarkCompress(b *testing.B) {
|
|
sizes := []int{
|
|
0,
|
|
256,
|
|
int(units.KiB),
|
|
int(units.MiB),
|
|
int(maxMessageSize),
|
|
}
|
|
for compressionType, newCompressorFunc := range newCompressorFuncs {
|
|
if compressionType == TypeNone {
|
|
continue
|
|
}
|
|
for _, size := range sizes {
|
|
b.Run(fmt.Sprintf("%s_%d", compressionType, size), func(b *testing.B) {
|
|
require := require.New(b)
|
|
|
|
bytes := utils.RandomBytes(size)
|
|
compressor, err := newCompressorFunc(maxMessageSize)
|
|
require.NoError(err)
|
|
for n := 0; n < b.N; n++ {
|
|
_, err := compressor.Compress(bytes)
|
|
require.NoError(err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
func BenchmarkDecompress(b *testing.B) {
|
|
sizes := []int{
|
|
0,
|
|
256,
|
|
int(units.KiB),
|
|
int(units.MiB),
|
|
int(maxMessageSize),
|
|
}
|
|
for compressionType, newCompressorFunc := range newCompressorFuncs {
|
|
if compressionType == TypeNone {
|
|
continue
|
|
}
|
|
for _, size := range sizes {
|
|
b.Run(fmt.Sprintf("%s_%d", compressionType, size), func(b *testing.B) {
|
|
require := require.New(b)
|
|
|
|
bytes := utils.RandomBytes(size)
|
|
compressor, err := newCompressorFunc(maxMessageSize)
|
|
require.NoError(err)
|
|
|
|
compressedBytes, err := compressor.Compress(bytes)
|
|
require.NoError(err)
|
|
|
|
for n := 0; n < b.N; n++ {
|
|
_, err := compressor.Decompress(compressedBytes)
|
|
require.NoError(err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|