-
Notifications
You must be signed in to change notification settings - Fork 1
/
dynssz.go
120 lines (103 loc) · 5.07 KB
/
dynssz.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
// Package dynssz provides dynamic SSZ (Simple Serialize) encoding and decoding for Ethereum data structures.
// Unlike static code generation approaches, dynssz uses runtime reflection to handle dynamic field sizes,
// making it suitable for various Ethereum presets beyond the mainnet. It seamlessly integrates with fastssz for
// optimal performance when static definitions are applicable.
//
// Copyright (c) 2024 by pk910. See LICENSE file for details.
package dynssz
import (
"fmt"
"reflect"
"sync"
)
type DynSsz struct {
fastsszCompatMutex sync.Mutex
fastsszCompatCache map[reflect.Type]*fastsszCompatibility
typeSizeMutex sync.RWMutex
typeSizeCache map[reflect.Type]*cachedSszSize
specValues map[string]any
specValueCache map[string]*cachedSpecValue
NoFastSsz bool
Verbose bool
}
// NewDynSsz creates a new instance of the DynSsz encoder/decoder.
// The 'specs' map contains dynamic properties and configurations that will be applied during SSZ serialization and deserialization processes.
// This allows for flexible and dynamic handling of SSZ encoding/decoding based on the given specifications, making it suitable for various Ethereum presets and custom scenarios.
// Returns a pointer to the newly created DynSsz instance, ready for use in serializing and deserializing operations.
func NewDynSsz(specs map[string]any) *DynSsz {
if specs == nil {
specs = map[string]any{}
}
return &DynSsz{
fastsszCompatCache: map[reflect.Type]*fastsszCompatibility{},
typeSizeCache: map[reflect.Type]*cachedSszSize{},
specValues: specs,
specValueCache: map[string]*cachedSpecValue{},
}
}
// MarshalSSZ serializes the given source into its SSZ (Simple Serialize) representation.
// It dynamically handles the serialization of types, including those with dynamic field sizes,
// by leveraging reflection at runtime. This method integrates with fastssz for types
// without dynamic specifications, optimizing performance. It returns the serialized data as a byte slice,
// or an error if serialization fails.
func (d *DynSsz) MarshalSSZ(source any) ([]byte, error) {
sourceType := reflect.TypeOf(source)
sourceValue := reflect.ValueOf(source)
size, err := d.getSszValueSize(sourceType, sourceValue, []sszSizeHint{})
if err != nil {
return nil, err
}
buf := make([]byte, 0, size)
newBuf, err := d.marshalType(sourceType, sourceValue, buf, []sszSizeHint{}, 0)
if err != nil {
return nil, err
}
if len(newBuf) != size {
return nil, fmt.Errorf("ssz length does not match expected length (expected: %v, got: %v)", size, len(newBuf))
}
return newBuf, nil
}
// MarshalSSZTo serializes the given source into its SSZ (Simple Serialize) representation and writes the output to the provided buffer.
// This method allows direct control over the serialization output buffer, allowing optimizations like buffer reuse.
// The 'source' parameter is the structure to be serialized, and 'buf' is the pre-allocated slice where the serialized data will be written.
// It dynamically handles serialization for types with dynamic field sizes, seamlessly integrating with fastssz when possible.
// Returns the updated buffer containing the serialized data and an error if serialization fails.
func (d *DynSsz) MarshalSSZTo(source any, buf []byte) ([]byte, error) {
sourceType := reflect.TypeOf(source)
sourceValue := reflect.ValueOf(source)
newBuf, err := d.marshalType(sourceType, sourceValue, buf, []sszSizeHint{}, 0)
if err != nil {
return nil, err
}
return newBuf, nil
}
// SizeSSZ calculates the size of the given source object when serialized using SSZ encoding.
// This function is useful for pre-determining the amount of space needed to serialize a given source object.
// The 'source' parameter can be any Go value. It dynamically evaluates the size, accommodating types
// with dynamic field sizes efficiently. Returns the calculated size as an int and an error if the process fails.
func (d *DynSsz) SizeSSZ(source any) (int, error) {
sourceType := reflect.TypeOf(source)
sourceValue := reflect.ValueOf(source)
size, err := d.getSszValueSize(sourceType, sourceValue, []sszSizeHint{})
if err != nil {
return 0, err
}
return size, nil
}
// UnmarshalSSZ decodes the given SSZ-encoded data into the target object.
// The 'ssz' byte slice contains the SSZ-encoded data, and 'target' is a pointer to the Go value that will hold the decoded data.
// This method dynamically handles the decoding, accommodating for types with dynamic field sizes.
// It seamlessly integrates with fastssz for types without dynamic specifications to ensure efficient decoding.
// Returns an error if decoding fails or if the provided ssz data has not been fully used for decoding.
func (d *DynSsz) UnmarshalSSZ(target any, ssz []byte) error {
targetType := reflect.TypeOf(target)
targetValue := reflect.ValueOf(target)
consumedBytes, err := d.unmarshalType(targetType, targetValue, ssz, []sszSizeHint{}, 0)
if err != nil {
return err
}
if consumedBytes != len(ssz) {
return fmt.Errorf("did not consume full ssz range (consumed: %v, ssz size: %v)", consumedBytes, len(ssz))
}
return nil
}