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  }