Skip to content

Commit 491d576

Browse files
author
Matthias Neugschwandtner
committed
[FAB-9784] Chaincode handler f. key-level metadata
Extending the chaincode handler and shim implementation to support setting and retrieval of per-key metadata. Change-Id: I95cd24c9dc34a3310e1c83a8e97ec638b14b0a8b Signed-off-by: Matthias Neugschwandtner <eug@zurich.ibm.com> Signed-off-by: Alessandro Sorniotti <ale.linux@sopit.net>
1 parent 70ef63c commit 491d576

File tree

6 files changed

+522
-16
lines changed

6 files changed

+522
-16
lines changed

core/chaincode/handler.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,10 @@ func (h *Handler) handleMessageReadyState(msg *pb.ChaincodeMessage) error {
205205
case pb.ChaincodeMessage_QUERY_STATE_CLOSE:
206206
go h.HandleTransaction(msg, h.HandleQueryStateClose)
207207

208+
case pb.ChaincodeMessage_GET_STATE_METADATA:
209+
go h.HandleTransaction(msg, h.HandleGetStateMetadata)
210+
case pb.ChaincodeMessage_PUT_STATE_METADATA:
211+
go h.HandleTransaction(msg, h.HandlePutStateMetadata)
208212
default:
209213
return fmt.Errorf("[%s] Fabric side handler cannot handle message (%s) while in ready state", msg.Txid, msg.Type)
210214
}
@@ -559,6 +563,40 @@ func (h *Handler) HandleGetState(msg *pb.ChaincodeMessage, txContext *Transactio
559563
return &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_RESPONSE, Payload: res, Txid: msg.Txid, ChannelId: msg.ChannelId}, nil
560564
}
561565

566+
// Handles query to ledger to get state metadata
567+
func (h *Handler) HandleGetStateMetadata(msg *pb.ChaincodeMessage, txContext *TransactionContext) (*pb.ChaincodeMessage, error) {
568+
getStateMetadata := &pb.GetStateMetadata{}
569+
err := proto.Unmarshal(msg.Payload, getStateMetadata)
570+
if err != nil {
571+
return nil, errors.Wrap(err, "unmarshal failed")
572+
}
573+
574+
chaincodeName := h.ChaincodeName()
575+
chaincodeLogger.Debugf("[%s] getting state metadata for chaincode %s, key %s, channel %s", shorttxid(msg.Txid), chaincodeName, getStateMetadata.Key, txContext.ChainID)
576+
577+
var metadata map[string][]byte
578+
if isCollectionSet(getStateMetadata.Collection) {
579+
metadata, err = txContext.TXSimulator.GetPrivateDataMetadata(chaincodeName, getStateMetadata.Collection, getStateMetadata.Key)
580+
} else {
581+
metadata, err = txContext.TXSimulator.GetStateMetadata(chaincodeName, getStateMetadata.Key)
582+
}
583+
if err != nil {
584+
return nil, errors.WithStack(err)
585+
}
586+
var metadataResult pb.StateMetadataResult
587+
for metakey := range metadata {
588+
md := &pb.StateMetadata{Metakey: metakey, Value: metadata[metakey]}
589+
metadataResult.Entries = append(metadataResult.Entries, md)
590+
}
591+
res, err := proto.Marshal(&metadataResult)
592+
if err != nil {
593+
return nil, errors.WithStack(err)
594+
}
595+
596+
// Send response msg back to chaincode. GetState will not trigger event
597+
return &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_RESPONSE, Payload: res, Txid: msg.Txid, ChannelId: msg.ChannelId}, nil
598+
}
599+
562600
// Handles query to ledger to rage query state
563601
func (h *Handler) HandleGetStateByRange(msg *pb.ChaincodeMessage, txContext *TransactionContext) (*pb.ChaincodeMessage, error) {
564602
getStateByRange := &pb.GetStateByRange{}
@@ -778,6 +816,29 @@ func (h *Handler) HandlePutState(msg *pb.ChaincodeMessage, txContext *Transactio
778816
return &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_RESPONSE, Txid: msg.Txid, ChannelId: msg.ChannelId}, nil
779817
}
780818

819+
func (h *Handler) HandlePutStateMetadata(msg *pb.ChaincodeMessage, txContext *TransactionContext) (*pb.ChaincodeMessage, error) {
820+
putStateMetadata := &pb.PutStateMetadata{}
821+
err := proto.Unmarshal(msg.Payload, putStateMetadata)
822+
if err != nil {
823+
return nil, errors.Wrap(err, "unmarshal failed")
824+
}
825+
826+
metadata := make(map[string][]byte)
827+
metadata[putStateMetadata.Metadata.Metakey] = putStateMetadata.Metadata.Value
828+
829+
chaincodeName := h.ChaincodeName()
830+
if isCollectionSet(putStateMetadata.Collection) {
831+
err = txContext.TXSimulator.SetPrivateDataMetadata(chaincodeName, putStateMetadata.Collection, putStateMetadata.Key, metadata)
832+
} else {
833+
err = txContext.TXSimulator.SetStateMetadata(chaincodeName, putStateMetadata.Key, metadata)
834+
}
835+
if err != nil {
836+
return nil, errors.WithStack(err)
837+
}
838+
839+
return &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_RESPONSE, Txid: msg.Txid, ChannelId: msg.ChannelId}, nil
840+
}
841+
781842
func (h *Handler) HandleDelState(msg *pb.ChaincodeMessage, txContext *TransactionContext) (*pb.ChaincodeMessage, error) {
782843
delState := &pb.DelState{}
783844
err := proto.Unmarshal(msg.Payload, delState)

core/chaincode/handler_test.go

Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,107 @@ var _ = Describe("Handler", func() {
480480
})
481481
})
482482

483+
Describe("HandlePutStateMetadata", func() {
484+
var incomingMessage *pb.ChaincodeMessage
485+
var request *pb.PutStateMetadata
486+
487+
BeforeEach(func() {
488+
request = &pb.PutStateMetadata{
489+
Key: "put-state-key",
490+
Metadata: &pb.StateMetadata{
491+
Metakey: "put-state-metakey",
492+
Value: []byte("put-state-metadata-value"),
493+
},
494+
}
495+
payload, err := proto.Marshal(request)
496+
Expect(err).NotTo(HaveOccurred())
497+
498+
incomingMessage = &pb.ChaincodeMessage{
499+
Type: pb.ChaincodeMessage_PUT_STATE_METADATA,
500+
Payload: payload,
501+
Txid: "tx-id",
502+
ChannelId: "channel-id",
503+
}
504+
})
505+
506+
It("returns a response message", func() {
507+
resp, err := handler.HandlePutStateMetadata(incomingMessage, txContext)
508+
Expect(err).NotTo(HaveOccurred())
509+
Expect(resp).To(Equal(&pb.ChaincodeMessage{
510+
Type: pb.ChaincodeMessage_RESPONSE,
511+
Txid: "tx-id",
512+
ChannelId: "channel-id",
513+
}))
514+
})
515+
516+
Context("when unmarshaling the request fails", func() {
517+
BeforeEach(func() {
518+
incomingMessage.Payload = []byte("this-is-a-bogus-payload")
519+
})
520+
521+
It("returns an error", func() {
522+
_, err := handler.HandlePutStateMetadata(incomingMessage, txContext)
523+
Expect(err).To(MatchError("unmarshal failed: proto: can't skip unknown wire type 4"))
524+
})
525+
})
526+
527+
Context("when the collection is not provided", func() {
528+
It("calls SetStateMetadata on the transaction simulator", func() {
529+
_, err := handler.HandlePutStateMetadata(incomingMessage, txContext)
530+
Expect(err).NotTo(HaveOccurred())
531+
532+
Expect(fakeTxSimulator.SetStateMetadataCallCount()).To(Equal(1))
533+
ccname, key, value := fakeTxSimulator.SetStateMetadataArgsForCall(0)
534+
Expect(ccname).To(Equal("cc-instance-name"))
535+
Expect(key).To(Equal("put-state-key"))
536+
Expect(value).To(Equal(map[string][]byte{"put-state-metakey": []byte("put-state-metadata-value")}))
537+
})
538+
539+
Context("when SetStateMetadata fails", func() {
540+
BeforeEach(func() {
541+
fakeTxSimulator.SetStateMetadataReturns(errors.New("king-kong"))
542+
})
543+
544+
It("returns an error", func() {
545+
_, err := handler.HandlePutStateMetadata(incomingMessage, txContext)
546+
Expect(err).To(MatchError("king-kong"))
547+
})
548+
})
549+
})
550+
551+
Context("when the collection is provided", func() {
552+
BeforeEach(func() {
553+
request.Collection = "collection-name"
554+
payload, err := proto.Marshal(request)
555+
Expect(err).NotTo(HaveOccurred())
556+
incomingMessage.Payload = payload
557+
})
558+
559+
It("calls SetPrivateDataMetadata on the transaction simulator", func() {
560+
_, err := handler.HandlePutStateMetadata(incomingMessage, txContext)
561+
Expect(err).NotTo(HaveOccurred())
562+
563+
Expect(fakeTxSimulator.SetPrivateDataMetadataCallCount()).To(Equal(1))
564+
ccname, collection, key, value := fakeTxSimulator.SetPrivateDataMetadataArgsForCall(0)
565+
Expect(ccname).To(Equal("cc-instance-name"))
566+
Expect(collection).To(Equal("collection-name"))
567+
Expect(key).To(Equal("put-state-key"))
568+
Expect(value).To(Equal(map[string][]byte{"put-state-metakey": []byte("put-state-metadata-value")}))
569+
})
570+
571+
Context("when SetPrivateDataMetadata fails", func() {
572+
BeforeEach(func() {
573+
fakeTxSimulator.SetPrivateDataMetadataReturns(errors.New("godzilla"))
574+
})
575+
576+
It("returns an error", func() {
577+
_, err := handler.HandlePutStateMetadata(incomingMessage, txContext)
578+
Expect(err).To(MatchError("godzilla"))
579+
})
580+
})
581+
})
582+
})
583+
483584
Describe("HandleDelState", func() {
484585
var incomingMessage *pb.ChaincodeMessage
485586
var request *pb.DelState
@@ -699,6 +800,134 @@ var _ = Describe("Handler", func() {
699800
})
700801
})
701802

803+
Describe("HandleGetStateMetadata", func() {
804+
var (
805+
incomingMessage *pb.ChaincodeMessage
806+
request *pb.GetStateMetadata
807+
expectedResponse *pb.ChaincodeMessage
808+
)
809+
810+
BeforeEach(func() {
811+
request = &pb.GetStateMetadata{
812+
Key: "get-state-key",
813+
}
814+
payload, err := proto.Marshal(request)
815+
Expect(err).NotTo(HaveOccurred())
816+
817+
incomingMessage = &pb.ChaincodeMessage{
818+
Type: pb.ChaincodeMessage_GET_STATE_METADATA,
819+
Payload: payload,
820+
Txid: "tx-id",
821+
ChannelId: "channel-id",
822+
}
823+
824+
expectedResponse = &pb.ChaincodeMessage{
825+
Type: pb.ChaincodeMessage_RESPONSE,
826+
Txid: "tx-id",
827+
ChannelId: "channel-id",
828+
}
829+
})
830+
831+
Context("when unmarshalling the request fails", func() {
832+
BeforeEach(func() {
833+
incomingMessage.Payload = []byte("this-is-a-bogus-payload")
834+
})
835+
836+
It("returns an error", func() {
837+
_, err := handler.HandleGetStateMetadata(incomingMessage, txContext)
838+
Expect(err).To(MatchError("unmarshal failed: proto: can't skip unknown wire type 4"))
839+
})
840+
})
841+
842+
Context("when collection is set", func() {
843+
BeforeEach(func() {
844+
request.Collection = "collection-name"
845+
payload, err := proto.Marshal(request)
846+
Expect(err).NotTo(HaveOccurred())
847+
incomingMessage.Payload = payload
848+
849+
fakeTxSimulator.GetPrivateDataMetadataReturns(map[string][]byte{"get-state-metakey": []byte("get-private-metadata-response")}, nil)
850+
responsePayload, err := proto.Marshal(&pb.StateMetadataResult{
851+
Entries: []*pb.StateMetadata{{
852+
Metakey: "get-state-metakey",
853+
Value: []byte("get-private-metadata-response"),
854+
}},
855+
})
856+
Expect(err).NotTo(HaveOccurred())
857+
expectedResponse.Payload = responsePayload
858+
})
859+
860+
It("calls GetPrivateDataMetadata on the transaction simulator", func() {
861+
_, err := handler.HandleGetStateMetadata(incomingMessage, txContext)
862+
Expect(err).NotTo(HaveOccurred())
863+
864+
Expect(fakeTxSimulator.GetPrivateDataMetadataCallCount()).To(Equal(1))
865+
ccname, collection, key := fakeTxSimulator.GetPrivateDataMetadataArgsForCall(0)
866+
Expect(ccname).To(Equal("cc-instance-name"))
867+
Expect(collection).To(Equal("collection-name"))
868+
Expect(key).To(Equal("get-state-key"))
869+
})
870+
871+
Context("and GetPrivateDataMetadata fails", func() {
872+
BeforeEach(func() {
873+
fakeTxSimulator.GetPrivateDataMetadataReturns(nil, errors.New("french fries"))
874+
})
875+
876+
It("returns the error from GetPrivateDataMetadata", func() {
877+
_, err := handler.HandleGetStateMetadata(incomingMessage, txContext)
878+
Expect(err).To(MatchError("french fries"))
879+
})
880+
})
881+
882+
It("returns the response message from GetPrivateDataMetadata", func() {
883+
resp, err := handler.HandleGetStateMetadata(incomingMessage, txContext)
884+
Expect(err).NotTo(HaveOccurred())
885+
Expect(resp).To(Equal(expectedResponse))
886+
})
887+
})
888+
889+
Context("when collection is not set", func() {
890+
BeforeEach(func() {
891+
fakeTxSimulator.GetStateMetadataReturns(map[string][]byte{"get-state-metakey": []byte("get-state-metadata-response")}, nil)
892+
responsePayload, err := proto.Marshal(&pb.StateMetadataResult{
893+
Entries: []*pb.StateMetadata{{
894+
Metakey: "get-state-metakey",
895+
Value: []byte("get-state-metadata-response"),
896+
}},
897+
})
898+
Expect(err).NotTo(HaveOccurred())
899+
expectedResponse.Payload = responsePayload
900+
})
901+
902+
It("calls GetStateMetadata on the transaction simulator", func() {
903+
_, err := handler.HandleGetStateMetadata(incomingMessage, txContext)
904+
Expect(err).NotTo(HaveOccurred())
905+
906+
Expect(fakeTxSimulator.GetStateMetadataCallCount()).To(Equal(1))
907+
ccname, key := fakeTxSimulator.GetStateMetadataArgsForCall(0)
908+
Expect(ccname).To(Equal("cc-instance-name"))
909+
Expect(key).To(Equal("get-state-key"))
910+
})
911+
912+
Context("and GetStateMetadata fails", func() {
913+
BeforeEach(func() {
914+
fakeTxSimulator.GetStateMetadataReturns(nil, errors.New("tomato"))
915+
})
916+
917+
It("returns the error from GetStateMetadata", func() {
918+
_, err := handler.HandleGetStateMetadata(incomingMessage, txContext)
919+
Expect(err).To(MatchError("tomato"))
920+
})
921+
})
922+
923+
It("returns the response from GetStateMetadata", func() {
924+
resp, err := handler.HandleGetStateMetadata(incomingMessage, txContext)
925+
Expect(err).NotTo(HaveOccurred())
926+
Expect(resp).To(Equal(expectedResponse))
927+
})
928+
})
929+
})
930+
702931
Describe("HandleGetStateByRange", func() {
703932
var (
704933
incomingMessage *pb.ChaincodeMessage

core/chaincode/shim/chaincode.go

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,14 @@ const (
5151
// ChaincodeStub is an object passed to chaincode for shim side handling of
5252
// APIs.
5353
type ChaincodeStub struct {
54-
TxID string
55-
ChannelId string
56-
chaincodeEvent *pb.ChaincodeEvent
57-
args [][]byte
58-
handler *Handler
59-
signedProposal *pb.SignedProposal
60-
proposal *pb.Proposal
54+
TxID string
55+
ChannelId string
56+
chaincodeEvent *pb.ChaincodeEvent
57+
args [][]byte
58+
handler *Handler
59+
signedProposal *pb.SignedProposal
60+
proposal *pb.Proposal
61+
validationParameterMetakey string
6162

6263
// Additional fields extracted from the signedProposal
6364
creator []byte
@@ -395,6 +396,7 @@ func (stub *ChaincodeStub) init(handler *Handler, channelId string, txid string,
395396
stub.handler = handler
396397
stub.signedProposal = signedProposal
397398
stub.decorations = input.Decorations
399+
stub.validationParameterMetakey = pb.MetaDataKeys_VALIDATION_PARAMETER.String()
398400

399401
// TODO: sanity check: verify that every call to init with a nil
400402
// signedProposal is a legitimate one, meaning it is an internal call
@@ -458,13 +460,18 @@ func (stub *ChaincodeStub) GetState(key string) ([]byte, error) {
458460

459461
// SetStateValidationParameter documentation can be found in interfaces.go
460462
func (stub *ChaincodeStub) SetStateValidationParameter(key string, ep []byte) error {
461-
// TODO: implement
462-
return nil
463+
return stub.handler.handlePutStateMetadataEntry("", key, stub.validationParameterMetakey, ep, stub.ChannelId, stub.TxID)
463464
}
464465

465466
// GetStateValidationParameter documentation can be found in interfaces.go
466467
func (stub *ChaincodeStub) GetStateValidationParameter(key string) ([]byte, error) {
467-
// TODO: implement
468+
md, err := stub.handler.handleGetStateMetadata("", key, stub.ChannelId, stub.TxID)
469+
if err != nil {
470+
return nil, err
471+
}
472+
if ep, ok := md[stub.validationParameterMetakey]; ok {
473+
return ep, nil
474+
}
468475
return nil, nil
469476
}
470477

@@ -569,14 +576,19 @@ func (stub *ChaincodeStub) GetPrivateDataQueryResult(collection, query string) (
569576

570577
// GetPrivateDataValidationParameter documentation can be found in interfaces.go
571578
func (stub *ChaincodeStub) GetPrivateDataValidationParameter(collection, key string) ([]byte, error) {
572-
// TODO: implement
579+
md, err := stub.handler.handleGetStateMetadata(collection, key, stub.ChannelId, stub.TxID)
580+
if err != nil {
581+
return nil, err
582+
}
583+
if ep, ok := md[stub.validationParameterMetakey]; ok {
584+
return ep, nil
585+
}
573586
return nil, nil
574587
}
575588

576589
// SetPrivateDataValidationParameter documentation can be found in interfaces.go
577590
func (stub *ChaincodeStub) SetPrivateDataValidationParameter(collection, key string, ep []byte) error {
578-
// TODO: implement
579-
return nil
591+
return stub.handler.handlePutStateMetadataEntry(collection, key, stub.validationParameterMetakey, ep, stub.ChannelId, stub.TxID)
580592
}
581593

582594
// CommonIterator documentation can be found in interfaces.go

0 commit comments

Comments
 (0)