dynssz

package module
v1.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 31, 2026 License: Apache-2.0 Imports: 13 Imported by: 16

README

Dynamic SSZ

Go Reference Go Report Card OpenSSF Scorecard codecov License

Dynamic SSZ is a Go library for SSZ encoding/decoding with support for dynamic field sizes and code generation. It provides runtime flexibility while maintaining high performance through optional static code generation.

Features

  • 🔧 Dynamic Field Sizes - Support for runtime-determined field sizes based on configuration
  • ⚡ Reflection-Based Processing - Works instantly with any SSZ-compatible types - no code generation required for prototyping
  • 🏗️ Code Generation - Optional static code generation for maximum performance (2-3x faster than dynamic processing)
  • 🚀 CLI Tool - Standalone dynssz-gen command for easy code generation from any Go package
  • 📡 Streaming Support - Memory-efficient streaming to/from io.Reader/io.Writer for large data
  • 🔄 Hybrid Approach - Seamlessly combines with fastssz for optimal efficiency
  • 👁️ SSZ Views - Support for multiple SSZ schemas on the same runtime type (useful for Ethereum fork handling)
  • 📦 Minimal Dependencies - Core library has minimal external dependencies
  • ✅ Spec Compliant - Fully compliant with SSZ specification and Ethereum consensus tests
  • 🧩 Extended Types - Optional support for signed integers, floats, big.Int, and optional types (non-standard)

Production Readiness

  • ✅ Reflection-based dynamic marshaling/unmarshaling/HTR: Production ready - battle-tested in various toolings and stable
  • ✅ Code generator: Production ready - feature complete and functionally verified through extensive fuzz testing, though less battle-tested in production environments compared to the reflection code paths

Quick Start

Installation
go get github.com/pk910/dynamic-ssz
Basic Usage
import dynssz "github.com/pk910/dynamic-ssz"

// Define your types with SSZ tags
type MyStruct struct {
    FixedArray  [32]byte
    DynamicList []uint64 `ssz-max:"1000"`
    ConfigBased []byte   `ssz-max:"1024" dynssz-max:"MAX_SIZE"`
}

// Create a DynSsz instance with your configuration
specs := map[string]any{
    "MAX_SIZE": uint64(2048),
}
ds := dynssz.NewDynSsz(specs)

// Marshal
data, err := ds.MarshalSSZ(myObject)

// Unmarshal
err = ds.UnmarshalSSZ(&myObject, data)

// Hash Tree Root
root, err := ds.HashTreeRoot(myObject)

The ssz-max and dynssz-max tags work together: ssz-max provides a static fallback, while dynssz-max references a spec value resolved at runtime. If the spec value is available it overrides the static default; otherwise the static value is used. This lets the same types work across different network presets (mainnet, minimal, custom testnets).

For maximum performance, use code generation with the dynssz-gen CLI tool:

go install github.com/pk910/dynamic-ssz/dynssz-gen@latest

Generate SSZ methods:

# Generate for types in current package
dynssz-gen -package . -types "MyStruct,OtherType" -output generated.go

# Generate for types in external package
dynssz-gen -package github.com/example/types -types "Block" -output block_ssz.go

Generated code produces optimized SSZ methods that eliminate reflection overhead. Important: Always use ds.MarshalSSZ(), ds.UnmarshalSSZ(), etc. as your entry points - the runtime automatically delegates to generated methods when available. Do not call generated methods (like MarshalSSZDyn) directly, as this creates a circular dependency that prevents regeneration. See the Code Generation Guide for details.

Performance

Dynamic SSZ is benchmarked against other SSZ libraries (including fastssz) in a dedicated benchmark repository: pk910/ssz-benchmark (view graphs).

SSZ Benchmark Results

The benchmarks compare encoding, decoding, and hash tree root performance across different SSZ libraries using common Ethereum consensus data structures.

View interactive benchmark results and historical trends at: https://pk910.github.io/ssz-benchmark/

Testing

The library includes comprehensive testing infrastructure:

  • Unit Tests: Fast, isolated tests for core functionality
  • Spec Tests: Ethereum consensus specification compliance tests
  • Fuzz Testing: Continuous fuzzing via CI that generates random SSZ type structures and verifies correctness by comparing reflection and codegen implementations across marshal, unmarshal, hash tree root, and streaming operations
  • Examples: Working examples that are automatically tested
  • Performance Tests: Benchmarking and regression testing

Documentation

Examples

Check out the examples directory for:

  • Basic encoding/decoding
  • Code generation setup
  • Ethereum types integration
  • Custom specifications
  • Multi-dimensional arrays

Contributing

We welcome contributions! Please see CONTRIBUTING.md for guidelines.

License

Dynamic SSZ is licensed under the Apache 2.0 License.

Documentation

Overview

Package dynssz provides dynamic SSZ encoding and decoding with runtime reflection support.

Package dynssz provides dynamic SSZ encoding and decoding with runtime reflection support.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func SetGlobalSpecs added in v1.0.2

func SetGlobalSpecs(specs map[string]any)

SetGlobalSpecs replaces the global DynSsz instance with a new one configured with the given specification values. Safe for concurrent use.

Types

type CallOption added in v1.3.0

type CallOption func(*callConfig)

CallOption is a functional option for per-call configuration of MarshalSSZ, UnmarshalSSZ, and HashTreeRoot operations. These options allow runtime customization of SSZ encoding behavior without modifying the DynSsz instance.

func WithViewDescriptor added in v1.3.0

func WithViewDescriptor(view any) CallOption

WithViewDescriptor specifies a view descriptor for fork-dependent SSZ schemas.

The view descriptor defines the SSZ layout (field order, tags, sizes) while the actual data is read from/written to the runtime object. This enables a single runtime type to support multiple SSZ representations for different forks.

The view parameter must be a struct or pointer to struct. Its fields are mapped to the runtime type's fields by name. The view's field types may differ from the runtime type's field types to support nested view descriptors.

When no view descriptor is provided, the runtime type itself is used as the schema.

Example usage:

// Define a view descriptor for Altair fork
type BodyAltairView struct {
    RandaoReveal   [96]byte
    SyncAggregate  SyncAggregateAltairView  // Nested view type
}

// Marshal with the Altair view
data, err := ds.MarshalSSZ(body, dynssz.WithViewDescriptor(&BodyAltairView{}))

// Unmarshal with the Altair view
err = ds.UnmarshalSSZ(&body, data, dynssz.WithViewDescriptor(&BodyAltairView{}))

// Compute hash tree root with the Altair view
root, err := ds.HashTreeRoot(body, dynssz.WithViewDescriptor(&BodyAltairView{}))

Note: The view descriptor value itself is not used for data storage; only its type information is used to determine the SSZ schema. You can pass a nil pointer of the view type: WithViewDescriptor((*BodyAltairView)(nil))

type CompatibleUnion added in v1.0.2

type CompatibleUnion[T any] struct {
	Variant uint8
	Data    interface{}
}

CompatibleUnion represents a union type that can hold one of several possible types. It uses Go generics where T is a descriptor struct that defines the union's possible types. The descriptor struct is never instantiated but provides type information through its fields.

The union stores: - unionType: uint8 field index indicating which variant is active - data: interface{} holding the actual value

Usage:

type UnionExecutionPayload = dynssz.CompatibleUnion[struct {
    ExecutionPayload
    ExecutionPayloadWithBlobs
}]

type BlockWithPayload struct {
    Slot          uint64
    ExecutionData UnionExecutionPayload
}

block := BlockWithPayload{
    Slot: 123,
    ExecutionData: UnionExecutionPayload{
        Variant: 0,
        Data: ExecutionPayload{
            ...
        },
    },
}

func NewCompatibleUnion added in v1.0.2

func NewCompatibleUnion[T any](variantIndex uint8, data interface{}) (*CompatibleUnion[T], error)

NewCompatibleUnion creates a new CompatibleUnion with the specified variant type and data. The variantIndex corresponds to the field index in the descriptor struct T.

func (*CompatibleUnion[T]) GetDescriptorType added in v1.0.2

func (u *CompatibleUnion[T]) GetDescriptorType() reflect.Type

GetDescriptorType returns the reflect.Type of the descriptor struct T. This allows external code to access the descriptor type information.

type DynSsz

type DynSsz struct {
	// contains filtered or unexported fields
}

DynSsz is a dynamic SSZ encoder/decoder that uses runtime reflection to handle dynamic field sizes. It provides flexible SSZ encoding/decoding for any Go data structures that can adapt to different specifications through dynamic field sizing. While commonly used with Ethereum data structures and presets (mainnet, minimal, custom), it works with any SSZ-compatible types.

The instance maintains caches for type descriptors and specification values to optimize performance. It's recommended to reuse the same DynSsz instance across operations to benefit from caching.

Key features:

  • Hybrid approach: automatically uses fastssz for static types, reflection for dynamic types
  • Type caching: reduces overhead for repeated operations on the same types
  • Specification support: handles dynamic field sizes based on runtime specifications
  • Thread-safe: can be safely used from multiple goroutines

Example usage:

specs := map[string]any{
    "SLOTS_PER_HISTORICAL_ROOT": uint64(8192),
    "SYNC_COMMITTEE_SIZE":       uint64(512),
}
ds := dynssz.NewDynSsz(specs)

// Marshal
data, err := ds.MarshalSSZ(myStruct)

// Unmarshal
err = ds.UnmarshalSSZ(&myStruct, data)

// Hash tree root
root, err := ds.HashTreeRoot(myStruct)

func GetGlobalDynSsz added in v1.0.2

func GetGlobalDynSsz() *DynSsz

GetGlobalDynSsz returns the global DynSsz instance, creating one with default settings if none exists. Safe for concurrent use.

func NewDynSsz

func NewDynSsz(specs map[string]any, options ...DynSszOption) *DynSsz

NewDynSsz creates a new instance of the DynSsz encoder/decoder.

The specs map contains dynamic properties and configurations that control SSZ serialization and deserialization. These specifications allow the library to handle different configurations by defining dynamic field sizes at runtime. While commonly used with Ethereum presets (mainnet, minimal, custom), they can define any dynamic sizing parameters for your data structures.

For non-Ethereum use cases, you can define any specifications relevant to your data structures.

The library supports mathematical expressions in dynssz-size tags that reference these specification values, enabling complex dynamic sizing behavior.

Parameters:

  • specs: A map of specification names to their values. Can be nil for default behavior.

Returns:

  • *DynSsz: A new DynSsz instance ready for encoding/decoding operations

Example:

// Ethereum mainnet specifications
specs := map[string]any{
    "SLOTS_PER_HISTORICAL_ROOT": uint64(8192),
    "SYNC_COMMITTEE_SIZE":       uint64(512),
}
ds := dynssz.NewDynSsz(specs)

// Custom application specifications
customSpecs := map[string]any{
    "MAX_ITEMS":           uint64(1000),
    "BUFFER_SIZE":         uint64(4096),
    "CUSTOM_ARRAY_LENGTH": uint64(256),
}
dsCustom := dynssz.NewDynSsz(customSpecs)

func (*DynSsz) GetTree added in v1.1.1

func (d *DynSsz) GetTree(source any, opts ...CallOption) (*treeproof.Node, error)

GetTree builds and returns the complete Merkle tree for the given value.

This method constructs a full Merkle tree representation of the SSZ-encoded structure, which is useful for proof generation, debugging, and understanding the internal tree structure. The returned tree can be used to generate Merkle proofs for any field or value within the structure.

The tree construction follows the same SSZ merkleization rules as HashTreeRoot, but instead of returning just the root hash, it provides access to the complete tree with all intermediate nodes. This enables:

  • Generating Merkle proofs for specific fields using tree.Prove(index)
  • Debugging tree structure with tree.Show(maxDepth)
  • Understanding how different fields map to generalized indices
  • Analyzing the progressive vs binary tree structures

Parameters:

  • source: Any Go value to be converted to a Merkle tree. Must be SSZ-compatible.

Returns:

  • *treeproof.Node: The root node of the complete Merkle tree
  • error: An error if tree construction fails due to unsupported types or encoding errors

The returned tree supports:

  • Navigation: Use Get(index) to fetch nodes by generalized index
  • Proof generation: Use Prove(index) to generate Merkle proofs
  • Debugging: Use Show(maxDepth) to visualize the tree structure
  • Multi-proofs: Use ProveMulti(indices) for efficient batch proofs

Example:

// Build tree for a beacon block
tree, err := ds.GetTree(beaconBlock)
if err != nil {
    log.Fatal("Failed to build tree:", err)
}

// Show tree structure (limited to 3 levels deep)
tree.Show(3)

// Generate proof for a specific field at generalized index 25
proof, err := tree.Prove(25)
if err != nil {
    log.Fatal("Failed to generate proof:", err)
}

// Verify the proof against the tree root
isValid, err := treeproof.VerifyProof(tree.Hash(), proof)

Note: For progressive containers (with ssz-index tags), the tree structure will be progressive rather than binary, which affects the generalized indices of fields.

func (*DynSsz) GetTypeCache added in v1.0.0

func (d *DynSsz) GetTypeCache() *ssztypes.TypeCache

GetTypeCache returns the type cache for the DynSsz instance.

The type cache stores computed type descriptors for types used in encoding/decoding operations. Type descriptors contain optimized information about how to serialize/deserialize specific types, including field offsets, size information, and whether fastssz can be used.

This method is primarily useful for debugging, performance analysis, or advanced use cases where you need to inspect or manage the cached type information.

Returns:

  • *TypeCache: The type cache instance containing all cached type descriptors

Example:

ds := dynssz.NewDynSsz(specs)
cache := ds.GetTypeCache()

// Inspect cached types
types := cache.GetAllTypes()
fmt.Printf("Cache contains %d types\n", len(types))

func (*DynSsz) HashTreeRoot added in v0.0.6

func (d *DynSsz) HashTreeRoot(source any, opts ...CallOption) ([32]byte, error)

HashTreeRoot computes the hash tree root of the given source object according to SSZ specifications.

The hash tree root is a cryptographic commitment to the entire data structure, used extensively in Ethereum's consensus layer for creating Merkle proofs and maintaining state roots. This method implements the SSZ hash tree root algorithm, which recursively hashes all fields and combines them using binary Merkle trees.

For optimal performance, the method uses a hasher pool to reuse hasher instances across calls. When NoFastHash is false (default), it uses the optimized gohashtree implementation. For types without dynamic fields, it automatically delegates to fastssz's HashTreeRoot method when available.

Parameters:

  • source: Any Go value for which to compute the hash tree root

Returns:

  • [32]byte: The computed hash tree root
  • error: An error if the computation fails due to unsupported types or hashing errors

The method handles all SSZ-supported types including:

  • Basic types (bool, uint8, uint16, uint32, uint64)
  • Fixed-size and variable-size arrays
  • Structs with nested fields
  • Slices with proper limit handling
  • Bitlists with maximum size constraints

Example:

block := &phase0.BeaconBlock{
    Slot:          12345,
    ProposerIndex: 42,
    // ... other fields
}

root, err := ds.HashTreeRoot(block)
if err != nil {
    log.Fatal("Failed to compute root:", err)
}
fmt.Printf("Block root: %x\n", root)

func (*DynSsz) HashTreeRootWith added in v1.1.1

func (d *DynSsz) HashTreeRootWith(source any, hh sszutils.HashWalker, opts ...CallOption) error

HashTreeRootWith computes the hash tree root of the given source object according to SSZ specifications.

This method is similar to HashTreeRoot, but allows for custom hasher instances to be used. It dynamically handles hashing for types with both static and dynamic field sizes, automatically using fastssz for optimal performance when applicable.

Parameters:

  • source: Any Go value for which to compute the hash tree root
  • hh: The HashWalker instance to use for hashing

Returns:

  • error: An error if the computation fails due to unsupported types or hashing errors

The method handles all SSZ-supported types including:

  • Basic types (bool, uint8, uint16, uint32, uint64)
  • Fixed-size and variable-size arrays
  • Structs with nested fields
  • Slices with proper limit handling
  • Bitlists with maximum size constraints

Example:

block := &phase0.BeaconBlock{
    Slot:          12345,
    ProposerIndex: 42,
    // ... other fields
}

hh := &hasher.Hasher{}
err := ds.HashTreeRootWith(block, hh)
if err != nil {
    log.Fatal("Failed to compute root:", err)
}
fmt.Printf("Block root: %x\n", hh.HashRoot())

func (*DynSsz) MarshalSSZ

func (d *DynSsz) MarshalSSZ(source any, opts ...CallOption) ([]byte, error)

MarshalSSZ serializes the given source into its SSZ (Simple Serialize) representation.

This method dynamically handles the serialization of Go types to SSZ format, supporting both static and dynamic field sizes. For types without dynamic specifications, it automatically uses fastssz for optimal performance. For types with dynamic field sizes (based on runtime specifications), it uses reflection-based processing.

The method allocates a new byte slice for the result. For high-performance scenarios with frequent allocations, consider using MarshalSSZTo with a pre-allocated buffer.

Parameters:

  • source: Any Go value to be serialized. Must be a type supported by SSZ encoding.

Returns:

  • []byte: The SSZ-encoded data as a new byte slice
  • error: An error if serialization fails due to unsupported types, encoding errors, or size mismatches

Supported types include:

  • Basic types: bool, uint8, uint16, uint32, uint64
  • Arrays and slices of supported types
  • Structs with appropriate SSZ tags
  • Pointers to supported types
  • Types implementing fastssz.Marshaler interface

Example:

header := &phase0.BeaconBlockHeader{
    Slot:          12345,
    ProposerIndex: 42,
    // ... other fields
}

data, err := ds.MarshalSSZ(header)
if err != nil {
    log.Fatal("Failed to marshal:", err)
}
fmt.Printf("Encoded %d bytes\n", len(data))

func (*DynSsz) MarshalSSZTo

func (d *DynSsz) MarshalSSZTo(source any, buf []byte, opts ...CallOption) ([]byte, error)

MarshalSSZTo serializes the given source into its SSZ (Simple Serialize) representation and writes the output to the provided buffer.

This method provides direct control over the output buffer, enabling performance optimizations such as buffer reuse across multiple serialization operations. Like MarshalSSZ, it dynamically handles serialization for types with both static and dynamic field sizes, automatically using fastssz when possible for optimal performance.

The method appends the serialized data to the provided buffer, which allows for efficient concatenation of multiple serialized objects without additional allocations.

Parameters:

  • source: Any Go value to be serialized. Must be a type supported by SSZ encoding.
  • buf: Pre-allocated byte slice where the serialized data will be appended. Can be nil or empty.

Returns:

  • []byte: The updated buffer containing the original data plus the newly serialized data
  • error: An error if serialization fails due to unsupported types, encoding errors, or size mismatches

Example:

buf := make([]byte, 0, 1024) // Pre-allocate with expected capacity
for _, block := range blocks {
    buf, err = ds.MarshalSSZTo(block, buf)
    if err != nil {
        log.Fatal("Failed to marshal block:", err)
    }
}
fmt.Printf("Serialized %d blocks into %d bytes\n", len(blocks), len(buf))

func (*DynSsz) MarshalSSZWriter added in v1.2.0

func (d *DynSsz) MarshalSSZWriter(source any, w io.Writer, opts ...CallOption) error

MarshalSSZWriter serializes the given source into its SSZ representation and writes it directly to an io.Writer.

This method provides memory-efficient streaming serialization for SSZ encoding, particularly beneficial for large data structures that would be expensive to buffer entirely in memory. Unlike MarshalSSZ which returns a complete byte slice, this method writes data incrementally to the provided writer, enabling direct output to files, network connections, or other I/O destinations.

The implementation employs several optimizations:

  • Internal buffering (default 1KB) to reduce system call overhead for small writes
  • Automatic delegation to regular MarshalSSZ for structures smaller than the buffer size
  • Pre-computed dynamic size trees for efficient offset calculation in complex structures
  • Seamless integration with fastssz for types without dynamic fields

For structures with dynamic fields, the method builds a size tree during the first pass to calculate all necessary offsets, then streams the actual data in a second pass. This two-pass approach ensures correct SSZ encoding while maintaining streaming efficiency.

Parameters:

  • source: Any Go value to be serialized. Must be a type supported by SSZ encoding.
  • w: The io.Writer destination for the SSZ-encoded output. Common writers include:
  • os.File for file output
  • net.Conn for network transmission
  • bytes.Buffer for in-memory buffering
  • Any custom io.Writer implementation

Returns:

  • error: An error if serialization fails due to:
  • Type validation errors
  • I/O write failures
  • Size calculation errors for dynamic fields
  • Unsupported type structures

Example usage:

// Write directly to a file
file, err := os.Create("beacon_state.ssz")
if err != nil {
    log.Fatal(err)
}
defer file.Close()

err = ds.MarshalSSZWriter(state, file)
if err != nil {
    log.Fatal("Failed to write state:", err)
}

// Stream over network
conn, err := net.Dial("tcp", "localhost:8080")
if err != nil {
    log.Fatal(err)
}
defer conn.Close()

err = ds.MarshalSSZWriter(block, conn)

func (*DynSsz) ResolveSpecValue added in v1.0.2

func (d *DynSsz) ResolveSpecValue(name string) (bool, uint64, error)

ResolveSpecValue resolves a dynamic specification value by name. The name can be a simple identifier (e.g., "MAX_VALIDATORS_PER_COMMITTEE") or a mathematical expression referencing spec values. Results are cached for subsequent lookups.

Returns whether the value was resolved, the uint64 value, and any parse error. If the name references undefined spec values, resolved will be false with no error.

func (*DynSsz) SizeSSZ

func (d *DynSsz) SizeSSZ(source any, opts ...CallOption) (int, error)

SizeSSZ calculates the size of the given source object when serialized using SSZ encoding.

This method is useful for pre-allocating buffers with the exact size needed for serialization, avoiding unnecessary allocations and resizing. It dynamically evaluates the size based on the actual values in the source object, accurately handling variable-length fields such as slices and dynamic arrays.

For types without dynamic fields, the size is calculated using the optimized fastssz SizeSSZ method when available. For types with dynamic fields, it traverses the entire structure to compute the exact serialized size.

Parameters:

  • source: Any Go value whose SSZ-encoded size needs to be calculated

Returns:

  • int: The exact number of bytes that would be produced by MarshalSSZ for this source
  • error: An error if the size calculation fails due to unsupported types or invalid data

Example:

state := &phase0.BeaconState{
    // ... populated state fields
}

size, err := ds.SizeSSZ(state)
if err != nil {
    log.Fatal("Failed to calculate size:", err)
}

// Pre-allocate buffer with exact size
buf := make([]byte, 0, size)
buf, err = ds.MarshalSSZTo(state, buf)

func (*DynSsz) UnmarshalSSZ

func (d *DynSsz) UnmarshalSSZ(target any, ssz []byte, opts ...CallOption) error

UnmarshalSSZ decodes the given SSZ-encoded data into the target object.

This method is the counterpart to MarshalSSZ, reconstructing Go values from their SSZ representation. It dynamically handles decoding for types with both static and dynamic field sizes, automatically using fastssz for optimal performance when applicable.

The target must be a pointer to a value of the appropriate type. The method will allocate memory for slices and initialize pointer fields as needed during decoding.

Parameters:

  • target: A pointer to the Go value where the decoded data will be stored. Must be a pointer.
  • ssz: The SSZ-encoded data to decode

Returns:

  • error: An error if decoding fails due to:
  • Invalid SSZ format
  • Type mismatches between the data and target
  • Insufficient or excess data
  • Unsupported types

The method ensures that all bytes in the ssz parameter are consumed during decoding. If there are leftover bytes, an error is returned indicating incomplete consumption.

Example:

var header phase0.BeaconBlockHeader
err := ds.UnmarshalSSZ(&header, encodedData)
if err != nil {
    log.Fatal("Failed to unmarshal:", err)
}
fmt.Printf("Decoded header for slot %d\n", header.Slot)

func (*DynSsz) UnmarshalSSZReader added in v1.2.0

func (d *DynSsz) UnmarshalSSZReader(target any, r io.Reader, size int, opts ...CallOption) error

UnmarshalSSZReader decodes SSZ-encoded data from an io.Reader directly into the target object.

This method implements memory-efficient streaming deserialization for SSZ data, reading incrementally from any io.Reader source. Unlike UnmarshalSSZ which requires the complete data in memory as a byte slice, this method processes data in chunks, making it ideal for large files, network streams, or memory-constrained environments.

The implementation handles SSZ's offset-based encoding for dynamic fields by:

  • Reading offsets to determine field boundaries for variable-length data
  • Using limited readers to enforce exact byte consumption per field
  • Processing static fields directly from the stream
  • Dynamically allocating slices based on discovered sizes

For optimal performance with small static types (≤ buffer size), the method automatically reads into an internal buffer and delegates to the regular unmarshal function.

Parameters:

  • target: A pointer to the Go value where decoded data will be stored. Must be a pointer to a type compatible with SSZ decoding. The method will allocate memory for slices and initialize pointer fields as needed during decoding.
  • r: An io.Reader source containing the SSZ-encoded data. Common readers include:
  • os.File for file input
  • net.Conn for network reception
  • bytes.Reader for in-memory data
  • Any custom io.Reader implementation
  • size: The expected total size of the SSZ data in bytes.

Returns:

  • error: An error if decoding fails due to:
  • I/O read failures
  • Invalid SSZ format or structure
  • Type mismatches between data and target
  • Unexpected EOF or excess data
  • Size constraint violations

The method ensures strict compliance with SSZ specifications, validating that:

  • All expected bytes are consumed (when size is specified)
  • Dynamic field offsets are valid and properly ordered
  • Field boundaries are respected
  • No data is left unread

Example usage:

// Read from file
file, err := os.Open("beacon_state.ssz")
if err != nil {
    log.Fatal(err)
}
defer file.Close()

// Get file size for exact reading
info, _ := file.Stat()
var state phase0.BeaconState
err = ds.UnmarshalSSZReader(&state, file, info.Size())
if err != nil {
    log.Fatal("Failed to read state:", err)
}

// Read from network with unknown size
conn, _ := net.Dial("tcp", "localhost:8080")
var block phase0.BeaconBlock
err = ds.UnmarshalSSZReader(&block, conn, -1)

func (*DynSsz) ValidateType added in v1.0.2

func (d *DynSsz) ValidateType(t reflect.Type, opts ...CallOption) error

ValidateType validates whether a given type is compatible with SSZ encoding/decoding.

This method performs a comprehensive analysis of the provided type to determine if it can be successfully serialized and deserialized according to SSZ specifications. It recursively validates all nested types within structs, arrays, and slices, ensuring complete compatibility throughout the type hierarchy.

The validation process checks for:

  • Supported primitive types (bool, uint8, uint16, uint32, uint64)
  • Valid composite types (arrays, slices, structs)
  • Proper SSZ tags on slice fields (ssz-size, ssz-max, dynssz-size, dynssz-max)
  • Correct tag syntax and values
  • No unsupported types (strings, maps, channels, signed integers, floats, etc.)

When a view descriptor is provided via WithViewDescriptor option, the method validates:

  • The schema type (view descriptor) is compatible with SSZ
  • Schema fields can be mapped to corresponding runtime fields by name
  • Field type compatibility between schema and runtime types

This method is particularly useful for:

  • Pre-validation before attempting marshalling/unmarshalling operations
  • Development-time type checking to catch errors early
  • Runtime validation of dynamically constructed types
  • Ensuring type compatibility when integrating with external systems
  • Validating view descriptor compatibility with runtime types

Parameters:

  • t: The reflect.Type to validate for SSZ compatibility
  • opts: Optional CallOptions, including WithViewDescriptor for fork-dependent validation

Returns:

  • error: nil if the type is valid for SSZ encoding/decoding, or a descriptive error explaining why the type is incompatible. The error message includes details about the specific field or type that caused the validation failure.

Example usage:

type MyStruct struct {
    ValidField   uint64
    InvalidField string  // This will cause validation to fail
}

err := ds.ValidateType(reflect.TypeOf(MyStruct{}))
if err != nil {
    log.Fatal("Type validation failed:", err)
    // Output: Type validation failed: field 'InvalidField': unsupported type 'string'
}

// With view descriptor validation:
err = ds.ValidateType(reflect.TypeOf(RuntimeBody{}), WithViewDescriptor(&AltairBodyView{}))

The method validates at the type level without requiring an instance of the type, making it suitable for early validation scenarios. For performance-critical paths, validation results can be cached as type compatibility doesn't change at runtime.

type DynSszOption added in v1.2.0

type DynSszOption func(*DynSszOptions)

DynSszOption is a functional option for configuring a DynSsz instance.

func WithExtendedTypes added in v1.2.2

func WithExtendedTypes() DynSszOption

WithExtendedTypes creates an option to enable extended type support.

When this option is enabled, dynssz will support nun-specified types like signed integers, floating point numbers, big integers and more. Generated SSZ code is incompatible with other SSZ libraries like fastssz.

func WithLogCb added in v1.2.0

func WithLogCb(logCb func(format string, args ...any)) DynSszOption

WithLogCb sets a custom logging callback for debug output during SSZ operations.

func WithNoFastHash added in v1.2.0

func WithNoFastHash() DynSszOption

WithNoFastHash disables the accelerated hashtree hashing library, falling back to the native Go sha256 implementation.

func WithNoFastSsz added in v1.2.0

func WithNoFastSsz() DynSszOption

WithNoFastSsz disables fastssz fallback for types that implement fastssz interfaces, forcing all operations through reflection-based encoding.

func WithStreamReaderBufferSize added in v1.3.0

func WithStreamReaderBufferSize(size int) DynSszOption

WithStreamReaderBufferSize sets the maximum internal buffer size for the streaming SSZ decoder used by UnmarshalSSZReader. Defaults to 2KB if not set.

func WithStreamWriterBufferSize added in v1.3.0

func WithStreamWriterBufferSize(size int) DynSszOption

WithStreamWriterBufferSize sets the internal buffer size for the streaming SSZ encoder used by MarshalSSZWriter. Defaults to 2KB if not set.

func WithVerbose added in v1.2.0

func WithVerbose() DynSszOption

WithVerbose enables verbose debug logging during SSZ operations.

type DynSszOptions added in v1.2.0

type DynSszOptions struct {
	NoFastSsz              bool
	NoFastHash             bool
	ExtendedTypes          bool
	Verbose                bool
	LogCb                  func(format string, args ...any)
	StreamWriterBufferSize int
	StreamReaderBufferSize int
}

DynSszOptions holds the configuration options for a DynSsz instance.

type TypeWrapper added in v1.0.2

type TypeWrapper[D, T any] struct {
	Data T
}

TypeWrapper represents a wrapper type that can provide SSZ annotations for non-struct types. It uses Go generics where D is a WrapperDescriptor struct that must have exactly 1 field, and T is the actual value type. The descriptor struct is never instantiated but provides type information with annotations.

The wrapper stores: - data: the actual value of type T

Usage:

type ByteSliceDescriptor struct {
    Data []byte `ssz-size:"32"`
}
type WrappedByteSlice = dynssz.TypeWrapper[ByteSliceDescriptor, []byte]

// Use in a struct or standalone
wrapped := WrappedByteSlice{}
wrapped.Set([]byte{1, 2, 3, 4})
data := wrapped.Get() // returns []byte

func NewTypeWrapper added in v1.0.2

func NewTypeWrapper[D, T any](data T) (*TypeWrapper[D, T], error)

NewTypeWrapper creates a new TypeWrapper with the specified data.

func (*TypeWrapper[D, T]) Get added in v1.0.2

func (w *TypeWrapper[D, T]) Get() T

Get returns the wrapped value.

func (*TypeWrapper[D, T]) GetDescriptorType added in v1.0.2

func (w *TypeWrapper[D, T]) GetDescriptorType() reflect.Type

GetDescriptorType returns the reflect.Type of the descriptor struct D. This allows external code to access the descriptor type information.

func (*TypeWrapper[D, T]) Set added in v1.0.2

func (w *TypeWrapper[D, T]) Set(value T)

Set sets the wrapped value.

Directories

Path Synopsis
Package codegen provides code generation for dynamic SSZ types.
Package codegen provides code generation for dynamic SSZ types.
Package main implements the dynssz-gen command.
Package main implements the dynssz-gen command.
Package reflection provides runtime reflection-based SSZ encoding, decoding, and hash tree root computation.
Package reflection provides runtime reflection-based SSZ encoding, decoding, and hash tree root computation.
Package ssztypes provides SSZ type descriptors, caching, and struct tag parsing for the dynamic-ssz library.
Package ssztypes provides SSZ type descriptors, caching, and struct tag parsing for the dynamic-ssz library.
Package sszutils provides shared interfaces, encoding/decoding primitives, and utility functions for SSZ (Simple Serialize) operations.
Package sszutils provides shared interfaces, encoding/decoding primitives, and utility functions for SSZ (Simple Serialize) operations.
test module
tests
fuzz/cmd/fuzz command
Command fuzz runs the SSZ fuzzer against generated corpus types.
Command fuzz runs the SSZ fuzzer against generated corpus types.
fuzz/cmd/generate command
Command generate creates random SSZ-compatible Go types, runs codegen on them, and produces a compilable corpus for the fuzzer.
Command generate creates random SSZ-compatible Go types, runs codegen on them, and produces a compilable corpus for the fuzzer.
fuzz/corpus
Package corpus contains generated SSZ types for fuzz testing.
Package corpus contains generated SSZ types for fuzz testing.
fuzz/engine
Package engine implements the core fuzzing logic for comparing SSZ reflection-based and codegen-based implementations.
Package engine implements the core fuzzing logic for comparing SSZ reflection-based and codegen-based implementations.
fuzz/typegen
Package typegen generates random SSZ-compatible Go struct definitions for fuzz testing.
Package typegen generates random SSZ-compatible Go struct definitions for fuzz testing.
Package treeproof provides Merkle tree construction and proof generation for SSZ structures.
Package treeproof provides Merkle tree construction and proof generation for SSZ structures.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL