github.com/hyperledger-labs/bdls@v2.1.1+incompatible/core/chaincode/lifecycle/serializer_test.go (about)

     1  /*
     2  Copyright IBM Corp. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package lifecycle_test
     8  
     9  import (
    10  	"fmt"
    11  
    12  	. "github.com/onsi/ginkgo"
    13  	. "github.com/onsi/gomega"
    14  
    15  	"github.com/golang/protobuf/proto"
    16  	lb "github.com/hyperledger/fabric-protos-go/peer/lifecycle"
    17  	"github.com/hyperledger/fabric/common/util"
    18  	"github.com/hyperledger/fabric/core/chaincode/lifecycle"
    19  	"github.com/hyperledger/fabric/core/chaincode/lifecycle/mock"
    20  	"github.com/hyperledger/fabric/protoutil"
    21  )
    22  
    23  var _ = Describe("Serializer", func() {
    24  	type TestStruct struct {
    25  		Int    int64
    26  		Bytes  []byte
    27  		Proto  *lb.InstallChaincodeResult
    28  		String string
    29  	}
    30  
    31  	var (
    32  		s          *lifecycle.Serializer
    33  		fakeState  *mock.ReadWritableState
    34  		testStruct *TestStruct
    35  	)
    36  
    37  	BeforeEach(func() {
    38  		fakeState = &mock.ReadWritableState{}
    39  
    40  		s = &lifecycle.Serializer{}
    41  
    42  		testStruct = &TestStruct{
    43  			Int:   -3,
    44  			Bytes: []byte("bytes"),
    45  			Proto: &lb.InstallChaincodeResult{
    46  				PackageId: "hash",
    47  			},
    48  			String: "theory",
    49  		}
    50  	})
    51  
    52  	Describe("Serialize", func() {
    53  		It("serializes the structure", func() {
    54  			err := s.Serialize("namespaces", "fake", testStruct, fakeState)
    55  			Expect(err).NotTo(HaveOccurred())
    56  
    57  			Expect(fakeState.GetStateCallCount()).To(Equal(1))
    58  			Expect(fakeState.GetStateArgsForCall(0)).To(Equal("namespaces/metadata/fake"))
    59  
    60  			Expect(fakeState.PutStateCallCount()).To(Equal(5))
    61  
    62  			key, value := fakeState.PutStateArgsForCall(0)
    63  			Expect(key).To(Equal("namespaces/fields/fake/Int"))
    64  			Expect(value).To(Equal(protoutil.MarshalOrPanic(&lb.StateData{
    65  				Type: &lb.StateData_Int64{Int64: -3},
    66  			})))
    67  
    68  			key, value = fakeState.PutStateArgsForCall(1)
    69  			Expect(key).To(Equal("namespaces/fields/fake/Bytes"))
    70  			Expect(value).To(Equal(protoutil.MarshalOrPanic(&lb.StateData{
    71  				Type: &lb.StateData_Bytes{Bytes: []byte("bytes")},
    72  			})))
    73  
    74  			key, value = fakeState.PutStateArgsForCall(2)
    75  			Expect(key).To(Equal("namespaces/fields/fake/Proto"))
    76  			Expect(value).To(Equal(protoutil.MarshalOrPanic(&lb.StateData{
    77  				Type: &lb.StateData_Bytes{Bytes: protoutil.MarshalOrPanic(testStruct.Proto)},
    78  			})))
    79  
    80  			key, value = fakeState.PutStateArgsForCall(3)
    81  			Expect(key).To(Equal("namespaces/fields/fake/String"))
    82  			Expect(value).To(Equal(protoutil.MarshalOrPanic(&lb.StateData{
    83  				Type: &lb.StateData_String_{String_: "theory"},
    84  			})))
    85  
    86  			key, value = fakeState.PutStateArgsForCall(4)
    87  			Expect(key).To(Equal("namespaces/metadata/fake"))
    88  			Expect(value).To(Equal(protoutil.MarshalOrPanic(&lb.StateMetadata{
    89  				Datatype: "TestStruct",
    90  				Fields:   []string{"Int", "Bytes", "Proto", "String"},
    91  			})))
    92  
    93  			Expect(fakeState.DelStateCallCount()).To(Equal(0))
    94  		})
    95  
    96  		Context("when the namespace contains extraneous keys", func() {
    97  			BeforeEach(func() {
    98  				kvs := map[string][]byte{
    99  					"namespaces/fields/fake/ExtraneousKey1": protoutil.MarshalOrPanic(&lb.StateData{
   100  						Type: &lb.StateData_Bytes{Bytes: []byte("value1")},
   101  					}),
   102  					"namespaces/fields/fake/ExtraneousKey2": protoutil.MarshalOrPanic(&lb.StateData{
   103  						Type: &lb.StateData_Bytes{Bytes: []byte("value2")},
   104  					}),
   105  					"namespaces/metadata/fake": protoutil.MarshalOrPanic(&lb.StateMetadata{
   106  						Datatype: "Other",
   107  						Fields:   []string{"ExtraneousKey1", "ExtraneousKey2"},
   108  					}),
   109  				}
   110  				fakeState.GetStateStub = func(key string) ([]byte, error) {
   111  					return kvs[key], nil
   112  				}
   113  			})
   114  
   115  			It("deletes them before returning", func() {
   116  				err := s.Serialize("namespaces", "fake", testStruct, fakeState)
   117  				Expect(err).NotTo(HaveOccurred())
   118  
   119  				Expect(fakeState.DelStateCallCount()).To(Equal(2))
   120  				Expect(map[string]struct{}{
   121  					fakeState.DelStateArgsForCall(0): {},
   122  					fakeState.DelStateArgsForCall(1): {},
   123  				}).To(Equal(map[string]struct{}{
   124  					"namespaces/fields/fake/ExtraneousKey1": {},
   125  					"namespaces/fields/fake/ExtraneousKey2": {},
   126  				}))
   127  			})
   128  
   129  			Context("when deleting from  the state fails", func() {
   130  				BeforeEach(func() {
   131  					fakeState.DelStateReturns(fmt.Errorf("del-error"))
   132  				})
   133  
   134  				It("deletes them before returning", func() {
   135  					err := s.Serialize("namespaces", "fake", testStruct, fakeState)
   136  					Expect(err.Error()).To(MatchRegexp("could not delete unneeded key namespaces/fields/fake/ExtraneousKey.: del-error"))
   137  				})
   138  			})
   139  		})
   140  
   141  		Context("when the namespace already contains the keys and values", func() {
   142  			var (
   143  				kvs map[string][]byte
   144  			)
   145  
   146  			BeforeEach(func() {
   147  				kvs = map[string][]byte{
   148  					"namespaces/fields/fake/Int": protoutil.MarshalOrPanic(&lb.StateData{
   149  						Type: &lb.StateData_Int64{Int64: -3},
   150  					}),
   151  					"namespaces/fields/fake/Bytes": protoutil.MarshalOrPanic(&lb.StateData{
   152  						Type: &lb.StateData_Bytes{Bytes: []byte("bytes")},
   153  					}),
   154  					"namespaces/fields/fake/Proto": protoutil.MarshalOrPanic(&lb.StateData{
   155  						Type: &lb.StateData_Bytes{Bytes: protoutil.MarshalOrPanic(testStruct.Proto)},
   156  					}),
   157  					"namespaces/fields/fake/String": protoutil.MarshalOrPanic(&lb.StateData{
   158  						Type: &lb.StateData_String_{String_: "theory"},
   159  					}),
   160  					"namespaces/metadata/fake": protoutil.MarshalOrPanic(&lb.StateMetadata{
   161  						Datatype: "TestStruct",
   162  						Fields:   []string{"Int", "Bytes", "Proto", "String"},
   163  					}),
   164  				}
   165  				fakeState.GetStateStub = func(key string) ([]byte, error) {
   166  					return kvs[key], nil
   167  				}
   168  			})
   169  
   170  			It("does not perform writes", func() {
   171  				err := s.Serialize("namespaces", "fake", testStruct, fakeState)
   172  				Expect(err).NotTo(HaveOccurred())
   173  
   174  				Expect(fakeState.PutStateCallCount()).To(Equal(0))
   175  				Expect(fakeState.DelStateCallCount()).To(Equal(0))
   176  			})
   177  
   178  			Context("when some of the values are missing", func() {
   179  				BeforeEach(func() {
   180  					kvs["namespaces/metadata/fake"] = protoutil.MarshalOrPanic(&lb.StateMetadata{
   181  						Datatype: "TestStruct",
   182  						Fields:   []string{"Proto", "Bytes"},
   183  					})
   184  					delete(kvs, "namespaces/fields/fake/Int")
   185  				})
   186  
   187  				It("writes the missing field and new metadata ", func() {
   188  					err := s.Serialize("namespaces", "fake", testStruct, fakeState)
   189  					Expect(err).NotTo(HaveOccurred())
   190  
   191  					Expect(fakeState.PutStateCallCount()).To(Equal(3))
   192  					key, value := fakeState.PutStateArgsForCall(0)
   193  					Expect(value).To(Equal(protoutil.MarshalOrPanic(&lb.StateData{
   194  						Type: &lb.StateData_Int64{Int64: -3},
   195  					})))
   196  					Expect(key).To(Equal("namespaces/fields/fake/Int"))
   197  					key, value = fakeState.PutStateArgsForCall(1)
   198  					Expect(key).To(Equal("namespaces/fields/fake/String"))
   199  					Expect(value).To(Equal(protoutil.MarshalOrPanic(&lb.StateData{
   200  						Type: &lb.StateData_String_{String_: "theory"},
   201  					})))
   202  					key, value = fakeState.PutStateArgsForCall(2)
   203  					Expect(key).To(Equal("namespaces/metadata/fake"))
   204  					Expect(value).To(Equal(protoutil.MarshalOrPanic(&lb.StateMetadata{
   205  						Datatype: "TestStruct",
   206  						Fields:   []string{"Int", "Bytes", "Proto", "String"},
   207  					})))
   208  					Expect(fakeState.DelStateCallCount()).To(Equal(0))
   209  				})
   210  			})
   211  
   212  			Context("when the namespace metadata is invalid", func() {
   213  				BeforeEach(func() {
   214  					kvs["namespaces/metadata/fake"] = []byte("bad-data")
   215  				})
   216  
   217  				It("wraps and returns the error", func() {
   218  					err := s.Serialize("namespaces", "fake", testStruct, fakeState)
   219  					Expect(err).To(MatchError("could not deserialize metadata for namespace namespaces/fake: could not unmarshal metadata for namespace namespaces/fake: unexpected EOF"))
   220  				})
   221  			})
   222  		})
   223  
   224  		Context("when the argument is not a pointer", func() {
   225  			It("fails", func() {
   226  				err := s.Serialize("namespaces", "fake", 8, fakeState)
   227  				Expect(err).To(MatchError("structure for namespace namespaces/fake is not serializable: must be pointer to struct, but got non-pointer int"))
   228  			})
   229  		})
   230  
   231  		Context("when the argument is a pointer to not-a-struct", func() {
   232  			It("fails", func() {
   233  				value := 7
   234  				err := s.Serialize("namespaces", "fake", &value, fakeState)
   235  				Expect(err).To(MatchError("structure for namespace namespaces/fake is not serializable: must be pointers to struct, but got pointer to int"))
   236  			})
   237  		})
   238  
   239  		Context("when the argument contains an illegal field type", func() {
   240  			It("it fails", func() {
   241  				type BadStruct struct {
   242  					BadField int
   243  				}
   244  
   245  				err := s.Serialize("namespaces", "fake", &BadStruct{}, fakeState)
   246  				Expect(err).To(MatchError("structure for namespace namespaces/fake is not serializable: unsupported structure field kind int for serialization for field BadField"))
   247  			})
   248  		})
   249  
   250  		Context("when the argument contains a non-byte slice", func() {
   251  			It("it fails", func() {
   252  				type BadStruct struct {
   253  					BadField []uint64
   254  				}
   255  
   256  				err := s.Serialize("namespaces", "fake", &BadStruct{}, fakeState)
   257  				Expect(err).To(MatchError("structure for namespace namespaces/fake is not serializable: unsupported slice type uint64 for field BadField"))
   258  			})
   259  		})
   260  
   261  		Context("when the argument contains a non-proto pointer", func() {
   262  			It("it fails", func() {
   263  				type BadStruct struct {
   264  					BadField *int
   265  				}
   266  
   267  				err := s.Serialize("namespaces", "fake", &BadStruct{}, fakeState)
   268  				Expect(err).To(MatchError("structure for namespace namespaces/fake is not serializable: unsupported pointer type int for field BadField (must be proto)"))
   269  			})
   270  		})
   271  
   272  		Context("when the state metadata cannot be retrieved", func() {
   273  			BeforeEach(func() {
   274  				fakeState.GetStateReturns(nil, fmt.Errorf("state-error"))
   275  			})
   276  
   277  			It("wraps and returns the error", func() {
   278  				err := s.Serialize("namespaces", "fake", testStruct, fakeState)
   279  				Expect(err).To(MatchError("could not deserialize metadata for namespace namespaces/fake: could not query metadata for namespace namespaces/fake: state-error"))
   280  			})
   281  		})
   282  
   283  		Context("when the field data cannot be retrieved", func() {
   284  			BeforeEach(func() {
   285  				fakeState.GetStateReturnsOnCall(0, protoutil.MarshalOrPanic(&lb.StateMetadata{
   286  					Fields: []string{"field1"},
   287  				}), nil)
   288  				fakeState.GetStateReturnsOnCall(1, nil, fmt.Errorf("state-error"))
   289  			})
   290  
   291  			It("wraps and returns the error", func() {
   292  				err := s.Serialize("namespaces", "fake", testStruct, fakeState)
   293  				Expect(err).To(MatchError("could not get value for key namespaces/fields/fake/field1: state-error"))
   294  			})
   295  		})
   296  
   297  		Context("when writing to the state for a field fails", func() {
   298  			BeforeEach(func() {
   299  				fakeState.PutStateReturns(fmt.Errorf("put-error"))
   300  			})
   301  
   302  			It("wraps and returns the error", func() {
   303  				err := s.Serialize("namespaces", "fake", testStruct, fakeState)
   304  				Expect(err).To(MatchError("could not write key into state: put-error"))
   305  			})
   306  		})
   307  
   308  		Context("when writing to the state for metadata fails", func() {
   309  			BeforeEach(func() {
   310  				fakeState.PutStateReturns(fmt.Errorf("put-error"))
   311  			})
   312  
   313  			It("wraps and returns the error", func() {
   314  				type Other struct{}
   315  				err := s.Serialize("namespaces", "fake", &Other{}, fakeState)
   316  				Expect(err).To(MatchError("could not store metadata for namespace namespaces/fake: put-error"))
   317  			})
   318  		})
   319  
   320  		Context("when marshaling a field fails", func() {
   321  			BeforeEach(func() {
   322  				s.Marshaler = func(msg proto.Message) ([]byte, error) {
   323  					if _, ok := msg.(*lb.InstallChaincodeResult); !ok {
   324  						return proto.Marshal(msg)
   325  					}
   326  					return nil, fmt.Errorf("marshal-error")
   327  				}
   328  			})
   329  
   330  			It("wraps and returns the error", func() {
   331  				err := s.Serialize("namespaces", "fake", testStruct, fakeState)
   332  				Expect(err).To(MatchError("could not marshal field Proto: marshal-error"))
   333  			})
   334  		})
   335  
   336  		Context("when marshaling a field fails", func() {
   337  			BeforeEach(func() {
   338  				s.Marshaler = func(msg proto.Message) ([]byte, error) {
   339  					return nil, fmt.Errorf("marshal-error")
   340  				}
   341  			})
   342  
   343  			It("wraps and returns the error", func() {
   344  				err := s.Serialize("namespaces", "fake", testStruct, fakeState)
   345  				Expect(err).To(MatchError("could not marshal value for key namespaces/fields/fake/Int: marshal-error"))
   346  			})
   347  		})
   348  
   349  		Context("when marshaling a the metadata fails", func() {
   350  			BeforeEach(func() {
   351  				s.Marshaler = func(msg proto.Message) ([]byte, error) {
   352  					return nil, fmt.Errorf("marshal-error")
   353  				}
   354  			})
   355  
   356  			It("wraps and returns the error", func() {
   357  				type Other struct{}
   358  				err := s.Serialize("namespaces", "fake", &Other{}, fakeState)
   359  				Expect(err).To(MatchError("could not marshal metadata for namespace namespaces/fake: marshal-error"))
   360  			})
   361  		})
   362  	})
   363  
   364  	Describe("Deserialize", func() {
   365  		var (
   366  			kvs      map[string][]byte
   367  			metadata *lb.StateMetadata
   368  		)
   369  
   370  		BeforeEach(func() {
   371  			metadata = &lb.StateMetadata{
   372  				Datatype: "TestStruct",
   373  				Fields:   []string{"Int", "Bytes", "Proto"},
   374  			}
   375  
   376  			kvs = map[string][]byte{
   377  				"namespaces/fields/fake/Int": protoutil.MarshalOrPanic(&lb.StateData{
   378  					Type: &lb.StateData_Int64{Int64: -3},
   379  				}),
   380  				"namespaces/fields/fake/Bytes": protoutil.MarshalOrPanic(&lb.StateData{
   381  					Type: &lb.StateData_Bytes{Bytes: []byte("bytes")},
   382  				}),
   383  				"namespaces/fields/fake/Proto": protoutil.MarshalOrPanic(&lb.StateData{
   384  					Type: &lb.StateData_Bytes{Bytes: protoutil.MarshalOrPanic(testStruct.Proto)},
   385  				}),
   386  				"namespaces/fields/fake/String": protoutil.MarshalOrPanic(&lb.StateData{
   387  					Type: &lb.StateData_String_{String_: "theory"},
   388  				}),
   389  			}
   390  
   391  			fakeState.GetStateStub = func(key string) ([]byte, error) {
   392  				fmt.Println("returning", kvs[key], "for", key)
   393  				return kvs[key], nil
   394  			}
   395  		})
   396  
   397  		It("populates the given struct with values from the state", func() {
   398  			target := &TestStruct{}
   399  			err := s.Deserialize("namespaces", "fake", metadata, target, fakeState)
   400  			Expect(err).NotTo(HaveOccurred())
   401  
   402  			Expect(fakeState.GetStateCallCount()).To(Equal(4))
   403  
   404  			Expect(target.Int).To(Equal(int64(-3)))
   405  			Expect(target.Bytes).To(Equal([]byte("bytes")))
   406  			Expect(target.String).To(Equal("theory"))
   407  			Expect(proto.Equal(target.Proto, testStruct.Proto)).To(BeTrue())
   408  		})
   409  
   410  		Context("when the field encoding is bad", func() {
   411  			BeforeEach(func() {
   412  				kvs["namespaces/fields/fake/Int"] = []byte("bad-data")
   413  			})
   414  
   415  			It("fails", func() {
   416  				testStruct := &TestStruct{}
   417  				err := s.Deserialize("namespaces", "fake", metadata, testStruct, fakeState)
   418  				Expect(err).To(MatchError("could not unmarshal state for key namespaces/fields/fake/Int: unexpected EOF"))
   419  			})
   420  		})
   421  
   422  		Context("when the int is not the correct type", func() {
   423  			BeforeEach(func() {
   424  				kvs["namespaces/fields/fake/Int"] = kvs["namespaces/fields/fake/Proto"]
   425  			})
   426  
   427  			It("fails", func() {
   428  				testStruct := &TestStruct{}
   429  				err := s.Deserialize("namespaces", "fake", metadata, testStruct, fakeState)
   430  				Expect(err).To(MatchError("expected key namespaces/fields/fake/Int to encode a value of type Int64, but was *lifecycle.StateData_Bytes"))
   431  			})
   432  		})
   433  
   434  		Context("when the bytes are not the correct type", func() {
   435  			BeforeEach(func() {
   436  				kvs["namespaces/fields/fake/Bytes"] = kvs["namespaces/fields/fake/Int"]
   437  			})
   438  
   439  			It("fails", func() {
   440  				testStruct := &TestStruct{}
   441  				err := s.Deserialize("namespaces", "fake", metadata, testStruct, fakeState)
   442  				Expect(err).To(MatchError("expected key namespaces/fields/fake/Bytes to encode a value of type []byte, but was *lifecycle.StateData_Int64"))
   443  			})
   444  		})
   445  
   446  		Context("when the proto is not the correct type", func() {
   447  			BeforeEach(func() {
   448  				kvs["namespaces/fields/fake/Proto"] = kvs["namespaces/fields/fake/Int"]
   449  			})
   450  
   451  			It("fails", func() {
   452  				testStruct := &TestStruct{}
   453  				err := s.Deserialize("namespaces", "fake", metadata, testStruct, fakeState)
   454  				Expect(err).To(MatchError("expected key namespaces/fields/fake/Proto to encode a value of type []byte, but was *lifecycle.StateData_Int64"))
   455  			})
   456  		})
   457  
   458  		Context("when the bytes are not the correct type", func() {
   459  			BeforeEach(func() {
   460  				kvs["namespaces/fields/fake/String"] = kvs["namespaces/fields/fake/Int"]
   461  			})
   462  
   463  			It("fails", func() {
   464  				testStruct := &TestStruct{}
   465  				err := s.Deserialize("namespaces", "fake", metadata, testStruct, fakeState)
   466  				Expect(err).To(MatchError("expected key namespaces/fields/fake/String to encode a value of type String, but was *lifecycle.StateData_Int64"))
   467  			})
   468  		})
   469  
   470  		Context("when the state cannot be queried", func() {
   471  			BeforeEach(func() {
   472  				fakeState.GetStateReturns(nil, fmt.Errorf("state-error"))
   473  			})
   474  
   475  			It("fails", func() {
   476  				testStruct := &TestStruct{}
   477  				err := s.Deserialize("namespaces", "fake", metadata, testStruct, fakeState)
   478  				Expect(err).To(MatchError("could not get state for key namespaces/fields/fake/Int: state-error"))
   479  			})
   480  		})
   481  
   482  		Context("when the argument is not a pointer", func() {
   483  			It("fails", func() {
   484  				err := s.Deserialize("namespaces", "fake", metadata, 8, fakeState)
   485  				Expect(err).To(MatchError("could not deserialize namespace namespaces/fake to unserializable type int: must be pointer to struct, but got non-pointer int"))
   486  			})
   487  		})
   488  
   489  		Context("when the argument is a pointer to not-a-struct", func() {
   490  			It("fails", func() {
   491  				value := 7
   492  				err := s.Deserialize("namespaces", "fake", metadata, &value, fakeState)
   493  				Expect(err).To(MatchError("could not deserialize namespace namespaces/fake to unserializable type *int: must be pointers to struct, but got pointer to int"))
   494  			})
   495  		})
   496  
   497  		Context("when the argument does not match the stored type", func() {
   498  			It("it fails", func() {
   499  				type Other struct{}
   500  				err := s.Deserialize("namespaces", "fake", metadata, &Other{}, fakeState)
   501  				Expect(err).To(MatchError("type name mismatch 'Other' != 'TestStruct'"))
   502  			})
   503  		})
   504  
   505  		Context("when the argument contains an illegal field type", func() {
   506  			BeforeEach(func() {
   507  				kvs["namespaces/metadata/fake"] = protoutil.MarshalOrPanic(&lb.StateMetadata{
   508  					Datatype: "BadStruct",
   509  				})
   510  			})
   511  
   512  			It("it fails", func() {
   513  				type BadStruct struct {
   514  					BadField int
   515  				}
   516  
   517  				err := s.Deserialize("namespaces", "fake", metadata, &BadStruct{}, fakeState)
   518  				Expect(err).To(MatchError("could not deserialize namespace namespaces/fake to unserializable type *lifecycle_test.BadStruct: unsupported structure field kind int for serialization for field BadField"))
   519  			})
   520  		})
   521  	})
   522  
   523  	Describe("Integration Round Trip of Serialize/Deserialize", func() {
   524  		var (
   525  			KVStore map[string][]byte
   526  		)
   527  
   528  		BeforeEach(func() {
   529  			KVStore = map[string][]byte{}
   530  
   531  			fakeState.PutStateStub = func(key string, value []byte) error {
   532  				KVStore[key] = value
   533  				return nil
   534  			}
   535  
   536  			fakeState.GetStateStub = func(key string) ([]byte, error) {
   537  				return KVStore[key], nil
   538  			}
   539  
   540  			fakeState.GetStateHashStub = func(key string) ([]byte, error) {
   541  				return util.ComputeSHA256(KVStore[key]), nil
   542  			}
   543  		})
   544  
   545  		It("deserializes to the same value that was serialized in", func() {
   546  			err := s.Serialize("namespace", "fake", testStruct, fakeState)
   547  			Expect(err).NotTo(HaveOccurred())
   548  
   549  			metadata, ok, err := s.DeserializeMetadata("namespace", "fake", fakeState)
   550  			Expect(err).NotTo(HaveOccurred())
   551  			Expect(ok).To(BeTrue())
   552  			deserialized := &TestStruct{}
   553  			err = s.Deserialize("namespace", "fake", metadata, deserialized, fakeState)
   554  			Expect(err).NotTo(HaveOccurred())
   555  
   556  			Expect(testStruct.Int).To(Equal(deserialized.Int))
   557  			Expect(proto.Equal(testStruct.Proto, deserialized.Proto))
   558  
   559  			matched, err := s.IsSerialized("namespace", "fake", testStruct, fakeState)
   560  			Expect(err).NotTo(HaveOccurred())
   561  			Expect(matched).To(BeTrue())
   562  		})
   563  	})
   564  
   565  	Describe("IsMetadataSerialized", func() {
   566  		var (
   567  			kvs map[string][]byte
   568  		)
   569  
   570  		BeforeEach(func() {
   571  			kvs = map[string][]byte{
   572  				"namespaces/metadata/fake": protoutil.MarshalOrPanic(&lb.StateMetadata{
   573  					Datatype: "TestStruct",
   574  					Fields:   []string{"Int", "Bytes", "Proto", "String"},
   575  				}),
   576  			}
   577  
   578  			fakeState.GetStateHashStub = func(key string) ([]byte, error) {
   579  				return util.ComputeSHA256(kvs[key]), nil
   580  			}
   581  		})
   582  
   583  		It("checks to see if the metadata type is stored in the opaque state", func() {
   584  			matched, err := s.IsMetadataSerialized("namespaces", "fake", testStruct, fakeState)
   585  			Expect(err).NotTo(HaveOccurred())
   586  			Expect(matched).To(BeTrue())
   587  
   588  			Expect(fakeState.GetStateHashCallCount()).To(Equal(1))
   589  			Expect(fakeState.GetStateHashArgsForCall(0)).To(Equal("namespaces/metadata/fake"))
   590  		})
   591  
   592  		Context("when the struct is not serializable", func() {
   593  			It("wraps and returns the error", func() {
   594  				_, err := s.IsMetadataSerialized("namespaces", "fake", nil, fakeState)
   595  				Expect(err).To(MatchError("structure for namespace namespaces/fake is not serializable: must be pointer to struct, but got non-pointer invalid"))
   596  			})
   597  		})
   598  
   599  		Context("when marshaling the metadata fails", func() {
   600  			BeforeEach(func() {
   601  				s.Marshaler = func(msg proto.Message) ([]byte, error) {
   602  					return nil, fmt.Errorf("marshal-error")
   603  				}
   604  			})
   605  
   606  			It("wraps and returns the error", func() {
   607  				type Other struct{}
   608  				_, err := s.IsMetadataSerialized("namespaces", "fake", &Other{}, fakeState)
   609  				Expect(err).To(MatchError("could not marshal metadata for namespace namespaces/fake: marshal-error"))
   610  			})
   611  		})
   612  	})
   613  
   614  	Describe("IsSerialized", func() {
   615  		var (
   616  			kvs map[string][]byte
   617  		)
   618  
   619  		BeforeEach(func() {
   620  			kvs = map[string][]byte{
   621  				"namespaces/fields/fake/Int": protoutil.MarshalOrPanic(&lb.StateData{
   622  					Type: &lb.StateData_Int64{Int64: -3},
   623  				}),
   624  				"namespaces/fields/fake/Bytes": protoutil.MarshalOrPanic(&lb.StateData{
   625  					Type: &lb.StateData_Bytes{Bytes: []byte("bytes")},
   626  				}),
   627  				"namespaces/fields/fake/Proto": protoutil.MarshalOrPanic(&lb.StateData{
   628  					Type: &lb.StateData_Bytes{Bytes: protoutil.MarshalOrPanic(testStruct.Proto)},
   629  				}),
   630  				"namespaces/fields/fake/String": protoutil.MarshalOrPanic(&lb.StateData{
   631  					Type: &lb.StateData_String_{String_: "theory"},
   632  				}),
   633  				"namespaces/metadata/fake": protoutil.MarshalOrPanic(&lb.StateMetadata{
   634  					Datatype: "TestStruct",
   635  					Fields:   []string{"Int", "Bytes", "Proto", "String"},
   636  				}),
   637  			}
   638  
   639  			fakeState.GetStateHashStub = func(key string) ([]byte, error) {
   640  				return util.ComputeSHA256(kvs[key]), nil
   641  			}
   642  		})
   643  
   644  		It("checks to see if the structure is stored in the opaque state", func() {
   645  			matched, err := s.IsSerialized("namespaces", "fake", testStruct, fakeState)
   646  			Expect(err).NotTo(HaveOccurred())
   647  			Expect(matched).To(BeTrue())
   648  
   649  			Expect(fakeState.GetStateHashCallCount()).To(Equal(5))
   650  			Expect(fakeState.GetStateHashArgsForCall(0)).To(Equal("namespaces/metadata/fake"))
   651  			Expect(fakeState.GetStateHashArgsForCall(1)).To(Equal("namespaces/fields/fake/Int"))
   652  			Expect(fakeState.GetStateHashArgsForCall(2)).To(Equal("namespaces/fields/fake/Bytes"))
   653  			Expect(fakeState.GetStateHashArgsForCall(3)).To(Equal("namespaces/fields/fake/Proto"))
   654  			Expect(fakeState.GetStateHashArgsForCall(4)).To(Equal("namespaces/fields/fake/String"))
   655  		})
   656  
   657  		Context("when the namespace contains extraneous keys", func() {
   658  			BeforeEach(func() {
   659  				kvs["namespaces/metadata/fake"] = protoutil.MarshalOrPanic(&lb.StateMetadata{
   660  					Datatype: "TestStruct",
   661  					Fields:   []string{"Int", "Uint", "String", "Bytes", "Other"},
   662  				})
   663  				kvs["namespaces/fields/fake/Other"] = protoutil.MarshalOrPanic(&lb.StateData{
   664  					Type: &lb.StateData_Bytes{Bytes: []byte("value1")},
   665  				})
   666  			})
   667  
   668  			It("returns a mismatch", func() {
   669  				matched, err := s.IsSerialized("namespaces", "fake", testStruct, fakeState)
   670  				Expect(err).NotTo(HaveOccurred())
   671  				Expect(matched).To(BeFalse())
   672  			})
   673  		})
   674  
   675  		Context("when the namespace contains different keys", func() {
   676  			BeforeEach(func() {
   677  				kvs["namespaces/fields/fake/Int"] = protoutil.MarshalOrPanic(&lb.StateData{
   678  					Type: &lb.StateData_Bytes{Bytes: []byte("value1")},
   679  				})
   680  			})
   681  
   682  			It("returns a mismatch", func() {
   683  				matched, err := s.IsSerialized("namespaces", "fake", testStruct, fakeState)
   684  				Expect(err).NotTo(HaveOccurred())
   685  				Expect(matched).To(BeFalse())
   686  			})
   687  		})
   688  
   689  		Context("when the argument is not a pointer", func() {
   690  			It("fails", func() {
   691  				_, err := s.IsSerialized("namespaces", "fake", 8, fakeState)
   692  				Expect(err).To(MatchError("structure for namespace namespaces/fake is not serializable: must be pointer to struct, but got non-pointer int"))
   693  			})
   694  		})
   695  
   696  		Context("when the namespace does not contains the keys and values", func() {
   697  			BeforeEach(func() {
   698  				kvs = map[string][]byte{}
   699  			})
   700  
   701  			It("returns a mismatch", func() {
   702  				matched, err := s.IsSerialized("namespaces", "fake", testStruct, fakeState)
   703  				Expect(err).NotTo(HaveOccurred())
   704  				Expect(matched).To(BeFalse())
   705  			})
   706  		})
   707  
   708  		Context("when the state metadata cannot be retrieved", func() {
   709  			BeforeEach(func() {
   710  				fakeState.GetStateHashReturns(nil, fmt.Errorf("state-error"))
   711  			})
   712  
   713  			It("wraps and returns the error", func() {
   714  				_, err := s.IsSerialized("namespaces", "fake", testStruct, fakeState)
   715  				Expect(err).To(MatchError("could not get value for key namespaces/metadata/fake: state-error"))
   716  			})
   717  		})
   718  
   719  		Context("when inner marshaling of a proto field fails", func() {
   720  			BeforeEach(func() {
   721  				s.Marshaler = func(msg proto.Message) ([]byte, error) {
   722  					if _, ok := msg.(*lb.StateMetadata); ok {
   723  						return proto.Marshal(msg)
   724  					}
   725  					if _, ok := msg.(*lb.StateData); ok {
   726  						return proto.Marshal(msg)
   727  					}
   728  					return nil, fmt.Errorf("marshal-error")
   729  				}
   730  			})
   731  
   732  			It("wraps and returns the error", func() {
   733  				_, err := s.IsSerialized("namespaces", "fake", testStruct, fakeState)
   734  				Expect(err).To(MatchError("could not marshal field Proto: marshal-error"))
   735  			})
   736  		})
   737  
   738  		Context("when marshaling a field fails", func() {
   739  			BeforeEach(func() {
   740  				s.Marshaler = func(msg proto.Message) ([]byte, error) {
   741  					if _, ok := msg.(*lb.StateData); ok {
   742  						return nil, fmt.Errorf("marshal-error")
   743  					}
   744  					return proto.Marshal(msg)
   745  				}
   746  			})
   747  
   748  			It("wraps and returns the error", func() {
   749  				_, err := s.IsSerialized("namespaces", "fake", testStruct, fakeState)
   750  				Expect(err).To(MatchError("could not marshal value for key namespaces/fields/fake/Int: marshal-error"))
   751  			})
   752  		})
   753  
   754  		Context("when marshaling a the metadata fails", func() {
   755  			BeforeEach(func() {
   756  				s.Marshaler = func(msg proto.Message) ([]byte, error) {
   757  					return nil, fmt.Errorf("marshal-error")
   758  				}
   759  			})
   760  
   761  			It("wraps and returns the error", func() {
   762  				type Other struct{}
   763  				_, err := s.IsSerialized("namespaces", "fake", &Other{}, fakeState)
   764  				Expect(err).To(MatchError("could not marshal metadata for namespace namespaces/fake: marshal-error"))
   765  			})
   766  		})
   767  	})
   768  
   769  	Describe("DeserializeAllMetadata", func() {
   770  		BeforeEach(func() {
   771  			fakeState.GetStateRangeReturns(map[string][]byte{
   772  				"namespaces/metadata/thing0": protoutil.MarshalOrPanic(&lb.StateMetadata{
   773  					Datatype: "TestDatatype0",
   774  				}),
   775  				"namespaces/metadata/thing1": protoutil.MarshalOrPanic(&lb.StateMetadata{
   776  					Datatype: "TestDatatype1",
   777  				}),
   778  				"namespaces/metadata/thing2": protoutil.MarshalOrPanic(&lb.StateMetadata{
   779  					Datatype: "TestDatatype2",
   780  				}),
   781  			}, nil)
   782  		})
   783  
   784  		It("deserializes all the metadata", func() {
   785  			result, err := s.DeserializeAllMetadata("namespaces", fakeState)
   786  			Expect(err).NotTo(HaveOccurred())
   787  			Expect(len(result)).To(Equal(3))
   788  			Expect(proto.Equal(result["thing0"], &lb.StateMetadata{Datatype: "TestDatatype0"})).To(BeTrue())
   789  			Expect(proto.Equal(result["thing1"], &lb.StateMetadata{Datatype: "TestDatatype1"})).To(BeTrue())
   790  			Expect(proto.Equal(result["thing2"], &lb.StateMetadata{Datatype: "TestDatatype2"})).To(BeTrue())
   791  
   792  			Expect(fakeState.GetStateRangeCallCount()).To(Equal(1))
   793  			Expect(fakeState.GetStateRangeArgsForCall(0)).To(Equal("namespaces/metadata/"))
   794  		})
   795  
   796  		Context("when GetStateRange returns an error", func() {
   797  			BeforeEach(func() {
   798  				fakeState.GetStateRangeReturns(nil, fmt.Errorf("get-state-range-error"))
   799  			})
   800  
   801  			It("wraps and returns the error", func() {
   802  				_, err := s.DeserializeAllMetadata("namespaces", fakeState)
   803  				Expect(err).To(MatchError("could not get state range for namespace namespaces: get-state-range-error"))
   804  			})
   805  		})
   806  
   807  		Context("when GetState returns nil", func() {
   808  			BeforeEach(func() {
   809  				fakeState.GetStateRangeReturns(nil, nil)
   810  			})
   811  
   812  			It("returns an empty map", func() {
   813  				result, err := s.DeserializeAllMetadata("namespaces", fakeState)
   814  				Expect(err).NotTo(HaveOccurred())
   815  				Expect(result).To(BeEmpty())
   816  			})
   817  		})
   818  
   819  		Context("when the metadata is invalid", func() {
   820  			BeforeEach(func() {
   821  				fakeState.GetStateRangeReturns(map[string][]byte{"namespaces/metadata/bad": []byte("bad-data")}, nil)
   822  			})
   823  
   824  			It("returns an error", func() {
   825  				_, err := s.DeserializeAllMetadata("namespaces", fakeState)
   826  				Expect(err).To(MatchError("error unmarshaling metadata for key namespaces/metadata/bad: unexpected EOF"))
   827  			})
   828  		})
   829  	})
   830  
   831  	Describe("DeserializeMetadata", func() {
   832  		BeforeEach(func() {
   833  			fakeState.GetStateReturns(protoutil.MarshalOrPanic(&lb.StateMetadata{
   834  				Datatype: "TestDatatype",
   835  			}), nil)
   836  		})
   837  
   838  		It("deserializes the metadata", func() {
   839  			result, ok, err := s.DeserializeMetadata("namespaces", "fake", fakeState)
   840  			Expect(err).NotTo(HaveOccurred())
   841  			Expect(ok).To(BeTrue())
   842  			Expect(proto.Equal(result, &lb.StateMetadata{Datatype: "TestDatatype"})).To(BeTrue())
   843  
   844  			Expect(fakeState.GetStateCallCount()).To(Equal(1))
   845  			Expect(fakeState.GetStateArgsForCall(0)).To(Equal("namespaces/metadata/fake"))
   846  		})
   847  
   848  		Context("when GetState returns an error", func() {
   849  			BeforeEach(func() {
   850  				fakeState.GetStateReturns(nil, fmt.Errorf("get-state-error"))
   851  			})
   852  
   853  			It("wraps and returns the error", func() {
   854  				_, _, err := s.DeserializeMetadata("namespaces", "fake", fakeState)
   855  				Expect(err).To(MatchError("could not query metadata for namespace namespaces/fake: get-state-error"))
   856  			})
   857  		})
   858  
   859  		Context("when GetState returns nil", func() {
   860  			BeforeEach(func() {
   861  				fakeState.GetStateReturns(nil, nil)
   862  			})
   863  
   864  			It("returns an error", func() {
   865  				_, ok, err := s.DeserializeMetadata("namespaces", "fake", fakeState)
   866  				Expect(err).NotTo(HaveOccurred())
   867  				Expect(ok).To(BeFalse())
   868  			})
   869  		})
   870  
   871  		Context("when the metadata is invalid", func() {
   872  			BeforeEach(func() {
   873  				fakeState.GetStateReturns([]byte("bad-data"), nil)
   874  			})
   875  
   876  			It("returns an error", func() {
   877  				_, _, err := s.DeserializeMetadata("namespaces", "fake", fakeState)
   878  				Expect(err).To(MatchError("could not unmarshal metadata for namespace namespaces/fake: unexpected EOF"))
   879  			})
   880  		})
   881  	})
   882  
   883  	Describe("DeserializeFieldAsBytes", func() {
   884  		BeforeEach(func() {
   885  			fakeState.GetStateReturns(protoutil.MarshalOrPanic(&lb.StateData{
   886  				Type: &lb.StateData_Bytes{Bytes: []byte("bytes")},
   887  			}), nil)
   888  		})
   889  
   890  		It("deserializes the field to a string", func() {
   891  			result, err := s.DeserializeFieldAsBytes("namespaces", "fake", "field", fakeState)
   892  			Expect(err).NotTo(HaveOccurred())
   893  			Expect(result).To(Equal([]byte("bytes")))
   894  
   895  			Expect(fakeState.GetStateCallCount()).To(Equal(1))
   896  			Expect(fakeState.GetStateArgsForCall(0)).To(Equal("namespaces/fields/fake/field"))
   897  		})
   898  
   899  		Context("when GetState returns an error", func() {
   900  			BeforeEach(func() {
   901  				fakeState.GetStateReturns(nil, fmt.Errorf("get-state-error"))
   902  			})
   903  
   904  			It("wraps and returns the error", func() {
   905  				_, err := s.DeserializeFieldAsBytes("namespaces", "fake", "field", fakeState)
   906  				Expect(err).To(MatchError("could not get state for key namespaces/fields/fake/field: get-state-error"))
   907  			})
   908  		})
   909  
   910  		Context("when GetState returns nil", func() {
   911  			BeforeEach(func() {
   912  				fakeState.GetStateReturns(nil, nil)
   913  			})
   914  
   915  			It("returns nil", func() {
   916  				result, err := s.DeserializeFieldAsBytes("namespaces", "fake", "field", fakeState)
   917  				Expect(err).NotTo(HaveOccurred())
   918  				Expect(result).To(BeNil())
   919  			})
   920  		})
   921  	})
   922  
   923  	Describe("DeserializeFieldAsProto", func() {
   924  		BeforeEach(func() {
   925  			fakeState.GetStateReturns(protoutil.MarshalOrPanic(&lb.StateData{
   926  				Type: &lb.StateData_Bytes{Bytes: protoutil.MarshalOrPanic(&lb.InstallChaincodeResult{PackageId: "hash"})},
   927  			}), nil)
   928  		})
   929  
   930  		It("deserializes the field to a string", func() {
   931  			result := &lb.InstallChaincodeResult{}
   932  			err := s.DeserializeFieldAsProto("namespaces", "fake", "field", fakeState, result)
   933  			Expect(err).NotTo(HaveOccurred())
   934  			Expect(proto.Equal(result, &lb.InstallChaincodeResult{PackageId: "hash"})).To(BeTrue())
   935  
   936  			Expect(fakeState.GetStateCallCount()).To(Equal(1))
   937  			Expect(fakeState.GetStateArgsForCall(0)).To(Equal("namespaces/fields/fake/field"))
   938  		})
   939  
   940  		Context("when GetState returns an error", func() {
   941  			BeforeEach(func() {
   942  				fakeState.GetStateReturns(nil, fmt.Errorf("get-state-error"))
   943  			})
   944  
   945  			It("wraps and returns the error", func() {
   946  				err := s.DeserializeFieldAsProto("namespaces", "fake", "field", fakeState, &lb.InstallChaincodeResult{})
   947  				Expect(err).To(MatchError("could not get state for key namespaces/fields/fake/field: get-state-error"))
   948  			})
   949  		})
   950  
   951  		Context("when GetState returns nil", func() {
   952  			BeforeEach(func() {
   953  				fakeState.GetStateReturns(nil, nil)
   954  			})
   955  
   956  			It("does nothing", func() {
   957  				result := &lb.InstallChaincodeResult{}
   958  				err := s.DeserializeFieldAsProto("namespaces", "fake", "field", fakeState, result)
   959  				Expect(err).NotTo(HaveOccurred())
   960  				Expect(proto.Equal(result, &lb.InstallChaincodeResult{})).To(BeTrue())
   961  			})
   962  		})
   963  
   964  		Context("when the db does not encode a proto", func() {
   965  			BeforeEach(func() {
   966  				fakeState.GetStateReturns(protoutil.MarshalOrPanic(&lb.StateData{
   967  					Type: &lb.StateData_Bytes{Bytes: []byte("garbage")},
   968  				}), nil)
   969  			})
   970  
   971  			It("wraps and returns the error", func() {
   972  				err := s.DeserializeFieldAsProto("namespaces", "fake", "field", fakeState, &lb.InstallChaincodeResult{})
   973  				Expect(err).To(MatchError("could not unmarshal key namespaces/fields/fake/field to *lifecycle.InstallChaincodeResult: proto: can't skip unknown wire type 7"))
   974  			})
   975  		})
   976  	})
   977  
   978  	Describe("DeserializeFieldAsInt64", func() {
   979  		BeforeEach(func() {
   980  			fakeState.GetStateReturns(protoutil.MarshalOrPanic(&lb.StateData{
   981  				Type: &lb.StateData_Int64{Int64: -3},
   982  			}), nil)
   983  		})
   984  
   985  		It("deserializes the field to a string", func() {
   986  			result, err := s.DeserializeFieldAsInt64("namespaces", "fake", "field", fakeState)
   987  			Expect(err).NotTo(HaveOccurred())
   988  			Expect(result).To(Equal(int64(-3)))
   989  
   990  			Expect(fakeState.GetStateCallCount()).To(Equal(1))
   991  			Expect(fakeState.GetStateArgsForCall(0)).To(Equal("namespaces/fields/fake/field"))
   992  		})
   993  
   994  		Context("when GetState returns an error", func() {
   995  			BeforeEach(func() {
   996  				fakeState.GetStateReturns(nil, fmt.Errorf("get-state-error"))
   997  			})
   998  
   999  			It("wraps and returns the error", func() {
  1000  				_, err := s.DeserializeFieldAsInt64("namespaces", "fake", "field", fakeState)
  1001  				Expect(err).To(MatchError("could not get state for key namespaces/fields/fake/field: get-state-error"))
  1002  			})
  1003  		})
  1004  
  1005  		Context("when GetState returns nil", func() {
  1006  			BeforeEach(func() {
  1007  				fakeState.GetStateReturns(nil, nil)
  1008  			})
  1009  
  1010  			It("returns nil", func() {
  1011  				result, err := s.DeserializeFieldAsInt64("namespaces", "fake", "field", fakeState)
  1012  				Expect(err).NotTo(HaveOccurred())
  1013  				Expect(result).To(Equal(int64(0)))
  1014  			})
  1015  		})
  1016  
  1017  		Context("when the field has trailing data", func() {
  1018  			BeforeEach(func() {
  1019  				fakeState.GetStateReturns([]byte("bad-data"), nil)
  1020  			})
  1021  
  1022  			It("returns an error", func() {
  1023  				_, err := s.DeserializeFieldAsInt64("namespaces", "fake", "field", fakeState)
  1024  				Expect(err).To(MatchError("could not unmarshal state for key namespaces/fields/fake/field: unexpected EOF"))
  1025  			})
  1026  		})
  1027  	})
  1028  
  1029  	Describe("DeserializeFieldAsString", func() {
  1030  		BeforeEach(func() {
  1031  			fakeState.GetStateReturns(protoutil.MarshalOrPanic(&lb.StateData{
  1032  				Type: &lb.StateData_String_{String_: "theory"},
  1033  			}), nil)
  1034  		})
  1035  
  1036  		It("deserializes the field to a string", func() {
  1037  			result, err := s.DeserializeFieldAsString("namespaces", "fake", "field", fakeState)
  1038  			Expect(err).NotTo(HaveOccurred())
  1039  			Expect(result).To(Equal("theory"))
  1040  
  1041  			Expect(fakeState.GetStateCallCount()).To(Equal(1))
  1042  			Expect(fakeState.GetStateArgsForCall(0)).To(Equal("namespaces/fields/fake/field"))
  1043  		})
  1044  
  1045  		Context("when GetState returns an error", func() {
  1046  			BeforeEach(func() {
  1047  				fakeState.GetStateReturns(nil, fmt.Errorf("get-state-error"))
  1048  			})
  1049  
  1050  			It("wraps and returns the error", func() {
  1051  				_, err := s.DeserializeFieldAsString("namespaces", "fake", "field", fakeState)
  1052  				Expect(err).To(MatchError("could not get state for key namespaces/fields/fake/field: get-state-error"))
  1053  			})
  1054  		})
  1055  
  1056  		Context("when GetState returns nil", func() {
  1057  			BeforeEach(func() {
  1058  				fakeState.GetStateReturns(nil, nil)
  1059  			})
  1060  
  1061  			It("returns nil", func() {
  1062  				result, err := s.DeserializeFieldAsString("namespaces", "fake", "field", fakeState)
  1063  				Expect(err).NotTo(HaveOccurred())
  1064  				Expect(result).To(Equal(""))
  1065  			})
  1066  		})
  1067  
  1068  		Context("when the field has trailing data", func() {
  1069  			BeforeEach(func() {
  1070  				fakeState.GetStateReturns([]byte("bad-data"), nil)
  1071  			})
  1072  
  1073  			It("returns an error", func() {
  1074  				_, err := s.DeserializeFieldAsString("namespaces", "fake", "field", fakeState)
  1075  				Expect(err).To(MatchError("could not unmarshal state for key namespaces/fields/fake/field: unexpected EOF"))
  1076  			})
  1077  		})
  1078  	})
  1079  })