github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/module/executiondatasync/execution_data/serializer.go (about) 1 package execution_data 2 3 import ( 4 "fmt" 5 "io" 6 "math" 7 8 cborlib "github.com/fxamacker/cbor/v2" 9 "github.com/ipfs/go-cid" 10 11 "github.com/onflow/flow-go/model/encoding" 12 "github.com/onflow/flow-go/model/encoding/cbor" 13 "github.com/onflow/flow-go/model/flow" 14 "github.com/onflow/flow-go/module/executiondatasync/execution_data/internal" 15 "github.com/onflow/flow-go/network" 16 "github.com/onflow/flow-go/network/compressor" 17 ) 18 19 // DefaultSerializer is the default implementation for an Execution Data serializer. 20 // It is configured to use cbor encoding with LZ4 compression. 21 var DefaultSerializer Serializer 22 23 func init() { 24 var codec encoding.Codec 25 26 decMode, err := cborlib.DecOptions{ 27 MaxArrayElements: math.MaxInt64, 28 MaxMapPairs: math.MaxInt64, 29 MaxNestedLevels: math.MaxInt16, 30 }.DecMode() 31 32 if err != nil { 33 panic(err) 34 } 35 36 codec = cbor.NewCodec(cbor.WithDecMode(decMode)) 37 DefaultSerializer = NewSerializer(codec, compressor.NewLz4Compressor()) 38 } 39 40 // header codes are used to distinguish between the different data types serialized within a blob. 41 // they provide simple versioning of execution state data blobs and indicate how the data should 42 // be deserialized back into their original form. Therefore, each input format must have a unique 43 // code, and the codes must never be reused. This allows libraries to accurately decode the data 44 // without juggling software versions. 45 const ( 46 codeRecursiveCIDs = iota + 1 47 codeExecutionDataRoot 48 codeChunkExecutionDataV1 49 codeChunkExecutionDataV2 // includes transaction results 50 ) 51 52 // getCode returns the header code for the given value's type. 53 // It returns an error if the type is not supported. 54 func getCode(v interface{}) (byte, error) { 55 switch v.(type) { 56 case *flow.BlockExecutionDataRoot: 57 return codeExecutionDataRoot, nil 58 case *internal.ChunkExecutionDataV1: // only used for backwards compatibility testing 59 return codeChunkExecutionDataV1, nil 60 case *ChunkExecutionData: 61 return codeChunkExecutionDataV2, nil 62 case []cid.Cid: 63 return codeRecursiveCIDs, nil 64 default: 65 return 0, fmt.Errorf("invalid type for interface: %T", v) 66 } 67 } 68 69 // getPrototype returns a new instance of the type that corresponds to the given header code. 70 // It returns an error if the code is not supported. 71 func getPrototype(code byte) (interface{}, error) { 72 switch code { 73 case codeExecutionDataRoot: 74 return &flow.BlockExecutionDataRoot{}, nil 75 case codeChunkExecutionDataV2, codeChunkExecutionDataV1: 76 return &ChunkExecutionData{}, nil // only return the latest version 77 case codeRecursiveCIDs: 78 return &[]cid.Cid{}, nil 79 default: 80 return nil, fmt.Errorf("invalid code: %v", code) 81 } 82 } 83 84 // Serializer is used to serialize / deserialize Execution Data and CID lists for the 85 // Execution Data Service. 86 type Serializer interface { 87 // Serialize encodes and compresses the given value to the given writer. 88 // No errors are expected during normal operation. 89 Serialize(io.Writer, interface{}) error 90 91 // Deserialize decompresses and decodes the data from the given reader. 92 // No errors are expected during normal operation. 93 Deserialize(io.Reader) (interface{}, error) 94 } 95 96 // serializer implements the Serializer interface. Object are serialized by encoding and 97 // compressing them using the given codec and compressor. 98 // 99 // The serialized data is prefixed with a single byte header that identifies the underlying 100 // data format. This allows adding new data types in a backwards compatible way. 101 type serializer struct { 102 codec encoding.Codec 103 compressor network.Compressor 104 } 105 106 // NewSerializer returns a new Execution Data serializer using the provided encoder and compressor. 107 func NewSerializer(codec encoding.Codec, compressor network.Compressor) *serializer { 108 return &serializer{ 109 codec: codec, 110 compressor: compressor, 111 } 112 } 113 114 // writePrototype writes the header code for the given value to the given writer 115 func (s *serializer) writePrototype(w io.Writer, v interface{}) error { 116 var code byte 117 var err error 118 119 if code, err = getCode(v); err != nil { 120 return err 121 } 122 123 if bw, ok := w.(io.ByteWriter); ok { 124 err = bw.WriteByte(code) 125 } else { 126 _, err = w.Write([]byte{code}) 127 } 128 129 if err != nil { 130 return fmt.Errorf("failed to write code: %w", err) 131 } 132 133 return nil 134 } 135 136 // Serialize encodes and compresses the given value to the given writer. 137 // No errors are expected during normal operation. 138 func (s *serializer) Serialize(w io.Writer, v interface{}) error { 139 if err := s.writePrototype(w, v); err != nil { 140 return fmt.Errorf("failed to write prototype: %w", err) 141 } 142 143 comp, err := s.compressor.NewWriter(w) 144 145 if err != nil { 146 return fmt.Errorf("failed to create compressor writer: %w", err) 147 } 148 149 enc := s.codec.NewEncoder(comp) 150 151 if err := enc.Encode(v); err != nil { 152 return fmt.Errorf("failed to encode data: %w", err) 153 } 154 155 // flush data out to the underlying writer 156 if err := comp.Close(); err != nil { 157 return fmt.Errorf("failed to close compressor: %w", err) 158 } 159 160 return nil 161 } 162 163 // readPrototype reads a header code from the given reader and returns a prototype value 164 func (s *serializer) readPrototype(r io.Reader) (interface{}, error) { 165 var code byte 166 var err error 167 168 if br, ok := r.(io.ByteReader); ok { 169 code, err = br.ReadByte() 170 } else { 171 var buf [1]byte 172 _, err = r.Read(buf[:]) 173 code = buf[0] 174 } 175 176 if err != nil { 177 return nil, fmt.Errorf("failed to read code: %w", err) 178 } 179 180 return getPrototype(code) 181 } 182 183 // Deserialize decompresses and decodes the data from the given reader. 184 // No errors are expected during normal operation. 185 func (s *serializer) Deserialize(r io.Reader) (interface{}, error) { 186 v, err := s.readPrototype(r) 187 188 if err != nil { 189 return nil, fmt.Errorf("failed to read prototype: %w", err) 190 } 191 192 comp, err := s.compressor.NewReader(r) 193 194 if err != nil { 195 return nil, fmt.Errorf("failed to create compressor reader: %w", err) 196 } 197 198 dec := s.codec.NewDecoder(comp) 199 200 if err := dec.Decode(v); err != nil { 201 return nil, fmt.Errorf("failed to decode data: %w", err) 202 } 203 204 return v, nil 205 }