From 3e53d8908fc3a8adc7171535d0644dd98c1dc65a Mon Sep 17 00:00:00 2001 From: Victor Nosov Date: Wed, 6 Nov 2019 15:23:36 +0300 Subject: [PATCH] Service response mutator (#29) cc service response mutator --- .../cpaper_asservice/bin/api/mock/main.go | 4 +- examples/cpaper_asservice/chaincode_test.go | 5 +- gateway/opt.go | 3 +- gateway/service/chaincode.pb.go | 159 +++++++++++++++--- gateway/service/chaincode.proto | 20 ++- gateway/service/chaincode.validator.pb.go | 30 ++-- gateway/service/errors.go | 4 + gateway/service/mock.go | 143 ---------------- gateway/service/mock/chaincode.go | 150 +++++++++++++++++ gateway/service/mock/mutator.go | 25 +++ gateway/service/service_test.go | 61 +++++++ identity/testdata/testdata.go | 3 +- testing/mockstub.go | 7 +- 13 files changed, 420 insertions(+), 194 deletions(-) delete mode 100644 gateway/service/mock.go create mode 100644 gateway/service/mock/chaincode.go create mode 100644 gateway/service/mock/mutator.go create mode 100644 gateway/service/service_test.go diff --git a/examples/cpaper_asservice/bin/api/mock/main.go b/examples/cpaper_asservice/bin/api/mock/main.go index c671bcc9..d5f11b2f 100644 --- a/examples/cpaper_asservice/bin/api/mock/main.go +++ b/examples/cpaper_asservice/bin/api/mock/main.go @@ -12,7 +12,7 @@ import ( "github.com/s7techlab/cckit/examples/cpaper_asservice" cpaperservice "github.com/s7techlab/cckit/examples/cpaper_asservice/service" "github.com/s7techlab/cckit/gateway" - "github.com/s7techlab/cckit/gateway/service" + "github.com/s7techlab/cckit/gateway/service/mock" "github.com/s7techlab/cckit/testing" "google.golang.org/grpc" ) @@ -42,7 +42,7 @@ func main() { cpaperMock := testing.NewMockStub(chaincodeName, cc) // Chaincode invocation service mock. For real network you can use example with hlf-sdk-go - cpaperMockService := service.NewMock().WithChannel(channelName, cpaperMock) + cpaperMockService := mock.New().WithChannel(channelName, cpaperMock) // default identity for signing requests to peeer (mocked) apiIdentity, err := testing.IdentityFromFile(`MSP`, `../../../testdata/admin.pem`, ioutil.ReadFile) diff --git a/examples/cpaper_asservice/chaincode_test.go b/examples/cpaper_asservice/chaincode_test.go index 973cd30c..2be385b5 100644 --- a/examples/cpaper_asservice/chaincode_test.go +++ b/examples/cpaper_asservice/chaincode_test.go @@ -6,12 +6,13 @@ import ( "testing" "time" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "github.com/golang/protobuf/ptypes" "github.com/golang/protobuf/ptypes/empty" "github.com/hyperledger/fabric/msp" "github.com/hyperledger/fabric/protos/peer" - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" "github.com/s7techlab/cckit/examples/cpaper_asservice" "github.com/s7techlab/cckit/examples/cpaper_asservice/schema" s "github.com/s7techlab/cckit/examples/cpaper_asservice/service" diff --git a/gateway/opt.go b/gateway/opt.go index 20fc53f6..907375a4 100644 --- a/gateway/opt.go +++ b/gateway/opt.go @@ -4,9 +4,8 @@ import ( "context" "github.com/hyperledger/fabric/msp" - "github.com/s7techlab/cckit/extensions/encryption" - "github.com/hyperledger/fabric/protos/peer" + "github.com/s7techlab/cckit/extensions/encryption" "github.com/s7techlab/cckit/gateway/service" ) diff --git a/gateway/service/chaincode.pb.go b/gateway/service/chaincode.pb.go index 33342e6b..1a65c81c 100644 --- a/gateway/service/chaincode.pb.go +++ b/gateway/service/chaincode.pb.go @@ -23,6 +23,31 @@ var _ = math.Inf // proto package needs to be updated. const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package +type InvocationType int32 + +const ( + InvocationType_QUERY InvocationType = 0 + InvocationType_INVOKE InvocationType = 1 +) + +var InvocationType_name = map[int32]string{ + 0: "QUERY", + 1: "INVOKE", +} + +var InvocationType_value = map[string]int32{ + "QUERY": 0, + "INVOKE": 1, +} + +func (x InvocationType) String() string { + return proto.EnumName(InvocationType_name, int32(x)) +} + +func (InvocationType) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_97136ef4b384cc22, []int{0} +} + type ChaincodeInput struct { // Chaincode name Chaincode string `protobuf:"bytes,1,opt,name=chaincode,proto3" json:"chaincode,omitempty"` @@ -142,37 +167,92 @@ func (m *ChaincodeLocator) GetChannel() string { return "" } +type ChaincodeExec struct { + Type InvocationType `protobuf:"varint,1,opt,name=type,proto3,enum=service.InvocationType" json:"type,omitempty"` + Input *ChaincodeInput `protobuf:"bytes,2,opt,name=input,proto3" json:"input,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ChaincodeExec) Reset() { *m = ChaincodeExec{} } +func (m *ChaincodeExec) String() string { return proto.CompactTextString(m) } +func (*ChaincodeExec) ProtoMessage() {} +func (*ChaincodeExec) Descriptor() ([]byte, []int) { + return fileDescriptor_97136ef4b384cc22, []int{2} +} + +func (m *ChaincodeExec) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ChaincodeExec.Unmarshal(m, b) +} +func (m *ChaincodeExec) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ChaincodeExec.Marshal(b, m, deterministic) +} +func (m *ChaincodeExec) XXX_Merge(src proto.Message) { + xxx_messageInfo_ChaincodeExec.Merge(m, src) +} +func (m *ChaincodeExec) XXX_Size() int { + return xxx_messageInfo_ChaincodeExec.Size(m) +} +func (m *ChaincodeExec) XXX_DiscardUnknown() { + xxx_messageInfo_ChaincodeExec.DiscardUnknown(m) +} + +var xxx_messageInfo_ChaincodeExec proto.InternalMessageInfo + +func (m *ChaincodeExec) GetType() InvocationType { + if m != nil { + return m.Type + } + return InvocationType_QUERY +} + +func (m *ChaincodeExec) GetInput() *ChaincodeInput { + if m != nil { + return m.Input + } + return nil +} + func init() { + proto.RegisterEnum("service.InvocationType", InvocationType_name, InvocationType_value) proto.RegisterType((*ChaincodeInput)(nil), "service.ChaincodeInput") proto.RegisterMapType((map[string][]byte)(nil), "service.ChaincodeInput.TransientEntry") proto.RegisterType((*ChaincodeLocator)(nil), "service.ChaincodeLocator") + proto.RegisterType((*ChaincodeExec)(nil), "service.ChaincodeExec") } func init() { proto.RegisterFile("chaincode.proto", fileDescriptor_97136ef4b384cc22) } var fileDescriptor_97136ef4b384cc22 = []byte{ - // 335 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x51, 0x4f, 0x4b, 0xfb, 0x40, - 0x10, 0x25, 0x4d, 0xff, 0x90, 0xf9, 0x95, 0xfe, 0xca, 0x22, 0x1a, 0x83, 0x87, 0xd2, 0x83, 0xf4, - 0x94, 0x48, 0xbd, 0x48, 0x55, 0x44, 0xb4, 0x87, 0x8a, 0x07, 0x0d, 0xde, 0xcb, 0x76, 0x3b, 0x36, - 0xa1, 0x71, 0x37, 0xec, 0x6e, 0x02, 0xf9, 0x7c, 0x7e, 0x11, 0x3f, 0x8a, 0x34, 0xdd, 0xa4, 0x14, - 0x11, 0xa4, 0xb7, 0x99, 0x7d, 0xf3, 0xde, 0xcc, 0xdb, 0x07, 0xff, 0x59, 0x44, 0x63, 0xce, 0xc4, - 0x12, 0xfd, 0x54, 0x0a, 0x2d, 0x48, 0x47, 0xa1, 0xcc, 0x63, 0x86, 0xde, 0xfd, 0x2a, 0xd6, 0x51, - 0xb6, 0xf0, 0x99, 0xf8, 0x08, 0xa2, 0x22, 0x45, 0x99, 0xe0, 0x72, 0x85, 0x32, 0x78, 0xa7, 0x0b, - 0x19, 0xb3, 0xa0, 0x9c, 0x56, 0x41, 0x8a, 0x28, 0x37, 0x75, 0x2a, 0x14, 0x4d, 0xe6, 0x12, 0x55, - 0x2a, 0xb8, 0x32, 0x5a, 0xde, 0xdd, 0xdf, 0x25, 0xea, 0x33, 0xe6, 0x98, 0x23, 0xd7, 0x5b, 0x81, - 0xe1, 0x97, 0x05, 0xbd, 0x87, 0x0a, 0x99, 0xf1, 0x34, 0xd3, 0xe4, 0x0c, 0x9c, 0x7a, 0xd6, 0xb5, - 0x06, 0xd6, 0xc8, 0x09, 0x77, 0x0f, 0xc4, 0x85, 0x0e, 0x8b, 0x28, 0xe7, 0x98, 0xb8, 0x8d, 0x12, - 0xab, 0x5a, 0x42, 0xa0, 0x49, 0xe5, 0x4a, 0xb9, 0xf6, 0xc0, 0x1e, 0x75, 0xc3, 0xb2, 0x26, 0x8f, - 0xe0, 0x68, 0x49, 0xb9, 0x8a, 0x91, 0x6b, 0xb7, 0x39, 0xb0, 0x47, 0xff, 0xc6, 0xe7, 0xbe, 0xf1, - 0xef, 0xef, 0xef, 0xf5, 0xdf, 0xaa, 0xc1, 0x29, 0xd7, 0xb2, 0x08, 0x77, 0x44, 0xef, 0x06, 0x7a, - 0xfb, 0x20, 0xe9, 0x83, 0xbd, 0xc6, 0xc2, 0x5c, 0xb7, 0x29, 0xc9, 0x11, 0xb4, 0x72, 0x9a, 0x64, - 0x58, 0x5e, 0xd5, 0x0d, 0xb7, 0xcd, 0xa4, 0x71, 0x65, 0x0d, 0x9f, 0xa0, 0x5f, 0x6f, 0x7a, 0x16, - 0x8c, 0x6a, 0x21, 0x0f, 0xf5, 0x38, 0xfe, 0xb4, 0xc0, 0xa9, 0xc5, 0xc8, 0x04, 0x5a, 0xaf, 0x19, - 0xca, 0x82, 0x9c, 0xfc, 0xe2, 0xc9, 0x73, 0xb7, 0xdf, 0xac, 0xfc, 0x17, 0x13, 0x60, 0x68, 0xf2, - 0x23, 0xd7, 0xd0, 0x9e, 0xf1, 0x5c, 0xac, 0xf1, 0x10, 0xf2, 0x2d, 0xb4, 0xa7, 0x9b, 0x10, 0x15, - 0x39, 0xfd, 0x49, 0x36, 0x1e, 0xbd, 0xe3, 0x8a, 0x5e, 0x23, 0x25, 0xe7, 0xc2, 0x5a, 0xb4, 0x4b, - 0xe0, 0xf2, 0x3b, 0x00, 0x00, 0xff, 0xff, 0x63, 0x4d, 0xd9, 0x47, 0x9b, 0x02, 0x00, 0x00, + // 424 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x52, 0x5d, 0x8b, 0x13, 0x31, + 0x14, 0x75, 0xb6, 0x1f, 0xcb, 0xdc, 0x5d, 0x6b, 0xb9, 0xc8, 0x3a, 0x0e, 0x3e, 0x94, 0x3e, 0x68, + 0x51, 0x9c, 0x4a, 0x7d, 0x59, 0x56, 0x45, 0x44, 0xe7, 0xa1, 0x2a, 0xea, 0x0e, 0xab, 0xe0, 0xd3, + 0x92, 0x66, 0xaf, 0xed, 0xd0, 0x9a, 0x84, 0x24, 0x2d, 0xce, 0xbf, 0xf5, 0x8f, 0x08, 0x92, 0xcc, + 0x74, 0xea, 0xb0, 0x28, 0xd2, 0xb7, 0x9b, 0xdc, 0x73, 0xce, 0x3d, 0x27, 0xb9, 0x70, 0x8b, 0x2f, + 0x58, 0x2e, 0xb8, 0xbc, 0xa2, 0x44, 0x69, 0x69, 0x25, 0x1e, 0x1a, 0xd2, 0x9b, 0x9c, 0x53, 0xfc, + 0x6a, 0x9e, 0xdb, 0xc5, 0x7a, 0x96, 0x70, 0xf9, 0x7d, 0xbc, 0x28, 0x14, 0xe9, 0x15, 0x5d, 0xcd, + 0x49, 0x8f, 0xbf, 0xb1, 0x99, 0xce, 0xf9, 0xd8, 0xa3, 0xcd, 0x58, 0x11, 0x69, 0x57, 0x2b, 0x69, + 0xd8, 0xea, 0x52, 0x93, 0x51, 0x52, 0x98, 0x4a, 0x2b, 0x7e, 0xf9, 0xff, 0x12, 0xb5, 0x8d, 0x4b, + 0xda, 0x90, 0xb0, 0xa5, 0xc0, 0xf0, 0x67, 0x00, 0xbd, 0xd7, 0xdb, 0xce, 0x54, 0xa8, 0xb5, 0xc5, + 0x7b, 0x10, 0xd6, 0xd8, 0x28, 0x18, 0x04, 0xa3, 0x30, 0xdb, 0x5d, 0x60, 0x04, 0x87, 0x7c, 0xc1, + 0x84, 0xa0, 0x55, 0x74, 0xe0, 0x7b, 0xdb, 0x23, 0x22, 0xb4, 0x99, 0x9e, 0x9b, 0xa8, 0x35, 0x68, + 0x8d, 0x8e, 0x33, 0x5f, 0xe3, 0x1b, 0x08, 0xad, 0x66, 0xc2, 0xe4, 0x24, 0x6c, 0xd4, 0x1e, 0xb4, + 0x46, 0x47, 0x93, 0xfb, 0x49, 0x95, 0x3f, 0x69, 0xce, 0x4d, 0x2e, 0xb6, 0xc0, 0x54, 0x58, 0x5d, + 0x64, 0x3b, 0x62, 0xfc, 0x1c, 0x7a, 0xcd, 0x26, 0xf6, 0xa1, 0xb5, 0xa4, 0xa2, 0x72, 0xe7, 0x4a, + 0xbc, 0x0d, 0x9d, 0x0d, 0x5b, 0xad, 0xc9, 0xbb, 0x3a, 0xce, 0xca, 0xc3, 0xd9, 0xc1, 0x69, 0x30, + 0x7c, 0x0b, 0xfd, 0x7a, 0xd2, 0x7b, 0xc9, 0x99, 0x95, 0x7a, 0xdf, 0x8c, 0xc3, 0x25, 0xdc, 0xac, + 0xb5, 0xd2, 0x1f, 0xc4, 0xf1, 0x11, 0xb4, 0x6d, 0xa1, 0x4a, 0x8d, 0xde, 0xe4, 0x4e, 0x9d, 0x6d, + 0x2a, 0x36, 0x6e, 0x54, 0x2e, 0xc5, 0x45, 0xa1, 0x28, 0xf3, 0x20, 0x7c, 0x0c, 0x9d, 0xdc, 0x45, + 0xf5, 0xaa, 0x47, 0x7f, 0xa0, 0x9b, 0x2f, 0x91, 0x95, 0xa8, 0x87, 0x0f, 0xa0, 0xd7, 0x94, 0xc1, + 0x10, 0x3a, 0xe7, 0x9f, 0xd3, 0xec, 0x6b, 0xff, 0x06, 0x02, 0x74, 0xa7, 0x1f, 0xbe, 0x7c, 0x7c, + 0x97, 0xf6, 0x83, 0xc9, 0xaf, 0x00, 0xc2, 0x5a, 0x02, 0x4f, 0xa1, 0xed, 0xad, 0x9d, 0x5c, 0x97, + 0x77, 0xf7, 0x71, 0x54, 0x7e, 0xbd, 0x49, 0x3e, 0x55, 0x4b, 0x95, 0x55, 0x3b, 0x85, 0x67, 0xd0, + 0x39, 0x5f, 0x93, 0x2e, 0xf0, 0x6f, 0xce, 0xfe, 0xc1, 0x7d, 0x06, 0x5d, 0x67, 0x76, 0x49, 0xfb, + 0x90, 0x5f, 0x40, 0x37, 0x75, 0x4b, 0x69, 0xf0, 0xee, 0x75, 0x72, 0xf5, 0x67, 0xf1, 0xc9, 0x96, + 0xbe, 0x8b, 0xe3, 0x38, 0x4f, 0x82, 0x59, 0xd7, 0x37, 0x9e, 0xfe, 0x0e, 0x00, 0x00, 0xff, 0xff, + 0x27, 0xc3, 0x64, 0xce, 0x6b, 0x03, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -187,6 +267,8 @@ const _ = grpc.SupportPackageIsVersion4 // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. type ChaincodeClient interface { + // Exec: Query or Invoke + Exec(ctx context.Context, in *ChaincodeExec, opts ...grpc.CallOption) (*peer.ProposalResponse, error) // Query chaincode on home peer. Do NOT send to orderer. Query(ctx context.Context, in *ChaincodeInput, opts ...grpc.CallOption) (*peer.ProposalResponse, error) // Invoke chaincode on peers, according to endorsement policy and the SEND to orderer @@ -203,6 +285,15 @@ func NewChaincodeClient(cc *grpc.ClientConn) ChaincodeClient { return &chaincodeClient{cc} } +func (c *chaincodeClient) Exec(ctx context.Context, in *ChaincodeExec, opts ...grpc.CallOption) (*peer.ProposalResponse, error) { + out := new(peer.ProposalResponse) + err := c.cc.Invoke(ctx, "/service.Chaincode/Exec", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *chaincodeClient) Query(ctx context.Context, in *ChaincodeInput, opts ...grpc.CallOption) (*peer.ProposalResponse, error) { out := new(peer.ProposalResponse) err := c.cc.Invoke(ctx, "/service.Chaincode/Query", in, out, opts...) @@ -255,6 +346,8 @@ func (x *chaincodeEventsClient) Recv() (*peer.ChaincodeEvent, error) { // ChaincodeServer is the server API for Chaincode service. type ChaincodeServer interface { + // Exec: Query or Invoke + Exec(context.Context, *ChaincodeExec) (*peer.ProposalResponse, error) // Query chaincode on home peer. Do NOT send to orderer. Query(context.Context, *ChaincodeInput) (*peer.ProposalResponse, error) // Invoke chaincode on peers, according to endorsement policy and the SEND to orderer @@ -267,6 +360,24 @@ func RegisterChaincodeServer(s *grpc.Server, srv ChaincodeServer) { s.RegisterService(&_Chaincode_serviceDesc, srv) } +func _Chaincode_Exec_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ChaincodeExec) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ChaincodeServer).Exec(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/service.Chaincode/Exec", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ChaincodeServer).Exec(ctx, req.(*ChaincodeExec)) + } + return interceptor(ctx, in, info, handler) +} + func _Chaincode_Query_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(ChaincodeInput) if err := dec(in); err != nil { @@ -328,6 +439,10 @@ var _Chaincode_serviceDesc = grpc.ServiceDesc{ ServiceName: "service.Chaincode", HandlerType: (*ChaincodeServer)(nil), Methods: []grpc.MethodDesc{ + { + MethodName: "Exec", + Handler: _Chaincode_Exec_Handler, + }, { MethodName: "Query", Handler: _Chaincode_Query_Handler, diff --git a/gateway/service/chaincode.proto b/gateway/service/chaincode.proto index d005bf90..34c448a6 100644 --- a/gateway/service/chaincode.proto +++ b/gateway/service/chaincode.proto @@ -5,11 +5,11 @@ package service; import "github.com/hyperledger/fabric/protos/peer/proposal_response.proto"; import "github.com/hyperledger/fabric/protos/peer/chaincode_event.proto"; -message ChaincodeInput { +message ChaincodeInput { // Chaincode name string chaincode = 1; // Channel name - string channel = 2; + string channel = 2; // Input contains the arguments for invocation. repeated bytes args = 3; @@ -21,17 +21,27 @@ message ChaincodeInput { map transient = 4; } - -message ChaincodeLocator { +message ChaincodeLocator { // Chaincode name string chaincode = 1; // Channel name - string channel = 2; + string channel = 2; +} + +enum InvocationType { + QUERY = 0; + INVOKE = 1; } +message ChaincodeExec { + InvocationType type = 1; + ChaincodeInput input = 2; +} // Chaincode invocation service service Chaincode { + // Exec: Query or Invoke + rpc Exec (ChaincodeExec) returns (protos.ProposalResponse); // Query chaincode on home peer. Do NOT send to orderer. rpc Query (ChaincodeInput) returns (protos.ProposalResponse); // Invoke chaincode on peers, according to endorsement policy and the SEND to orderer diff --git a/gateway/service/chaincode.validator.pb.go b/gateway/service/chaincode.validator.pb.go index e33824f2..8d8c01e7 100644 --- a/gateway/service/chaincode.validator.pb.go +++ b/gateway/service/chaincode.validator.pb.go @@ -1,23 +1,15 @@ // Code generated by protoc-gen-gogo. DO NOT EDIT. // source: chaincode.proto -/* -Package service is a generated protocol buffer package. - -It is generated from these files: - chaincode.proto - -It has these top-level messages: - ChaincodeInput - ChaincodeLocator -*/ package service -import proto "github.com/golang/protobuf/proto" -import fmt "fmt" -import math "math" -import _ "github.com/hyperledger/fabric/protos/peer" -import _ "github.com/hyperledger/fabric/protos/peer" +import ( + fmt "fmt" + proto "github.com/golang/protobuf/proto" + _ "github.com/hyperledger/fabric/protos/peer" + github_com_mwitkow_go_proto_validators "github.com/mwitkow/go-proto-validators" + math "math" +) // Reference imports to suppress errors if they are not otherwise used. var _ = proto.Marshal @@ -31,3 +23,11 @@ func (this *ChaincodeInput) Validate() error { func (this *ChaincodeLocator) Validate() error { return nil } +func (this *ChaincodeExec) Validate() error { + if this.Input != nil { + if err := github_com_mwitkow_go_proto_validators.CallValidatorIfExists(this.Input); err != nil { + return github_com_mwitkow_go_proto_validators.FieldError("Input", err) + } + } + return nil +} diff --git a/gateway/service/errors.go b/gateway/service/errors.go index 928ce6b8..e13b38da 100644 --- a/gateway/service/errors.go +++ b/gateway/service/errors.go @@ -6,5 +6,9 @@ var ( // ErrChaincodeNotExists occurs when attempting to invoke a nonexostent external chaincode ErrChaincodeNotExists = errors.New(`chaincode not exists`) + // ErrSignerNotDefinedInContext msp.SigningIdentity is not defined in context ErrSignerNotDefinedInContext = errors.New(`signer is not defined in context`) + + // ErrUnknownInvocationType query or invoke + ErrUnknownInvocationType = errors.New(`unknown invocation type`) ) diff --git a/gateway/service/mock.go b/gateway/service/mock.go deleted file mode 100644 index 6117e106..00000000 --- a/gateway/service/mock.go +++ /dev/null @@ -1,143 +0,0 @@ -package service - -import ( - "context" - "errors" - "fmt" - "sync" - - "github.com/hyperledger/fabric/core/chaincode/shim" - "github.com/hyperledger/fabric/msp" - "github.com/hyperledger/fabric/protos/peer" - "github.com/s7techlab/cckit/testing" -) - -const ( - MessageProtocolVersion = 1 -) - -type ( - ChannelMockStubs map[string]*testing.MockStub - - ChannelsMockStubs map[string]ChannelMockStubs - - MockChaincodeService struct { - // channel name -> chaincode name - ChannelCC ChannelsMockStubs - m sync.Mutex - } -) - -func NewMock() *MockChaincodeService { - return &MockChaincodeService{ - ChannelCC: make(ChannelsMockStubs), - } -} -func (cs *MockChaincodeService) Query(ctx context.Context, in *ChaincodeInput) (proposalResponse *peer.ProposalResponse, err error) { - var ( - mockStub *testing.MockStub - signer msp.SigningIdentity - response peer.Response - ) - - cs.m.Lock() - defer cs.m.Unlock() - - if mockStub, err = cs.Chaincode(in.Channel, in.Chaincode); err != nil { - return - } - - if signer, err = SignerFromContext(ctx); err != nil { - return - } - - if response = mockStub.From(signer).WithTransient(in.Transient).QueryBytes(in.Args...); response.Status == shim.ERROR { - return nil, errors.New(response.Message) - } - - return &peer.ProposalResponse{ - Version: MessageProtocolVersion, - Timestamp: mockStub.TxTimestamp, - Response: &response, - }, nil -} - -func (cs *MockChaincodeService) Invoke(ctx context.Context, in *ChaincodeInput) (proposalResponse *peer.ProposalResponse, err error) { - var ( - mockStub *testing.MockStub - signer msp.SigningIdentity - response peer.Response - ) - - cs.m.Lock() - defer cs.m.Unlock() - - if mockStub, err = cs.Chaincode(in.Channel, in.Chaincode); err != nil { - return - } - - if signer, err = SignerFromContext(ctx); err != nil { - return - } - - if response = mockStub.From(signer).WithTransient(in.Transient).InvokeBytes(in.Args...); response.Status == shim.ERROR { - return nil, errors.New(response.Message) - } - - return &peer.ProposalResponse{ - Version: MessageProtocolVersion, - Timestamp: mockStub.TxTimestamp, - Response: &response, - }, nil - -} - -func (cs *MockChaincodeService) Events(in *ChaincodeLocator, stream Chaincode_EventsServer) (err error) { - var ( - mockStub *testing.MockStub - ) - if mockStub, err = cs.Chaincode(in.Channel, in.Chaincode); err != nil { - return - } - events := mockStub.EventSubscription() - for { - e, ok := <-events - if !ok { - return nil - } - if err = stream.Send(e); err != nil { - return err - } - } -} - -func (cs *MockChaincodeService) WithChannel(channel string, mockStubs ...*testing.MockStub) *MockChaincodeService { - if _, ok := cs.ChannelCC[channel]; !ok { - cs.ChannelCC[channel] = make(ChannelMockStubs) - } - - for _, ms := range mockStubs { - cs.ChannelCC[channel][ms.Name] = ms - for chName, chnl := range cs.ChannelCC { - for ccName, cc := range chnl { - - // add visibility of added cc to all other cc - cc.MockPeerChaincode(ms.Name+`/`+channel, ms) - - // add visibility of other cc to added cc - ms.MockPeerChaincode(ccName+`/`+chName, cc) - } - } - } - - return cs -} - -func (cs *MockChaincodeService) Chaincode(channel string, chaincode string) (*testing.MockStub, error) { - ms, exists := cs.ChannelCC[channel][chaincode] - if !exists { - return nil, fmt.Errorf(`%s: channell=%s, chaincode=%s`, ErrChaincodeNotExists, channel, chaincode) - } - - return ms, nil -} diff --git a/gateway/service/mock/chaincode.go b/gateway/service/mock/chaincode.go new file mode 100644 index 00000000..fda98adc --- /dev/null +++ b/gateway/service/mock/chaincode.go @@ -0,0 +1,150 @@ +package mock + +import ( + "context" + "errors" + "fmt" + "sync" + + "github.com/hyperledger/fabric/core/chaincode/shim" + "github.com/hyperledger/fabric/msp" + "github.com/hyperledger/fabric/protos/peer" + "github.com/s7techlab/cckit/gateway/service" + "github.com/s7techlab/cckit/testing" +) + +const ( + MessageProtocolVersion = 1 +) + +type ( + Channel map[string]*testing.MockStub + + Channels map[string]Channel + + ChaincodeService struct { + // channel name -> chaincode name + ChannelCC Channels + m sync.Mutex + ResponseMutator ResponseMutator + } + + // ResponseMutator allows to imitate peer errors or unavailability + ResponseMutator func(in *service.ChaincodeExec, r peer.Response) (peer.Response, error) +) + +func New() *ChaincodeService { + return &ChaincodeService{ + ChannelCC: make(Channels), + } +} + +func (cs *ChaincodeService) Exec(ctx context.Context, in *service.ChaincodeExec) (*peer.ProposalResponse, error) { + var ( + mockStub *testing.MockStub + signer msp.SigningIdentity + response peer.Response + err error + ) + + cs.m.Lock() + defer cs.m.Unlock() + + if mockStub, err = cs.Chaincode(in.Input.Channel, in.Input.Chaincode); err != nil { + return nil, err + } + if signer, err = service.SignerFromContext(ctx); err != nil { + return nil, err + } + + mockStub.From(signer).WithTransient(in.Input.Transient) + + if in.Type == service.InvocationType_QUERY { + response = mockStub.QueryBytes(in.Input.Args...) + } else if in.Type == service.InvocationType_INVOKE { + response = mockStub.InvokeBytes(in.Input.Args...) + } else { + return nil, service.ErrUnknownInvocationType + } + + if cs.ResponseMutator != nil { + if response, err = cs.ResponseMutator(in, response); err != nil { + return nil, err + } + } + + if response.Status == shim.ERROR { + return nil, errors.New(response.Message) + } + + return &peer.ProposalResponse{ + Version: MessageProtocolVersion, + Timestamp: mockStub.TxTimestamp, + Response: &response, + }, nil +} + +func (cs *ChaincodeService) Query(ctx context.Context, in *service.ChaincodeInput) (*peer.ProposalResponse, error) { + return cs.Exec(ctx, &service.ChaincodeExec{ + Type: service.InvocationType_QUERY, + Input: in, + }) +} + +func (cs *ChaincodeService) Invoke(ctx context.Context, in *service.ChaincodeInput) (proposalResponse *peer.ProposalResponse, err error) { + return cs.Exec(ctx, &service.ChaincodeExec{ + Type: service.InvocationType_QUERY, + Input: in, + }) +} + +func (cs *ChaincodeService) Events(in *service.ChaincodeLocator, stream service.Chaincode_EventsServer) (err error) { + var ( + mockStub *testing.MockStub + ) + if mockStub, err = cs.Chaincode(in.Channel, in.Chaincode); err != nil { + return + } + events := mockStub.EventSubscription() + for { + e, ok := <-events + if !ok { + return nil + } + if err = stream.Send(e); err != nil { + return err + } + } +} + +func (cs *ChaincodeService) WithChannel(channel string, mockStubs ...*testing.MockStub) *ChaincodeService { + if _, ok := cs.ChannelCC[channel]; !ok { + cs.ChannelCC[channel] = make(Channel) + } + + for _, ms := range mockStubs { + cs.ChannelCC[channel][ms.Name] = ms + for chName, chnl := range cs.ChannelCC { + for ccName, cc := range chnl { + + // add visibility of added cc to all other cc + cc.MockPeerChaincode(ms.Name+`/`+channel, ms) + + // add visibility of other cc to added cc + ms.MockPeerChaincode(ccName+`/`+chName, cc) + } + } + } + + return cs +} + +func (cs *ChaincodeService) Chaincode(channel string, chaincode string) (*testing.MockStub, error) { + ms, exists := cs.ChannelCC[channel][chaincode] + if !exists { + return nil, fmt.Errorf(`%s: channell=%s, chaincode=%s`, + service.ErrChaincodeNotExists, channel, chaincode) + } + + return ms, nil +} diff --git a/gateway/service/mock/mutator.go b/gateway/service/mock/mutator.go new file mode 100644 index 00000000..a4d50ba2 --- /dev/null +++ b/gateway/service/mock/mutator.go @@ -0,0 +1,25 @@ +package mock + +import ( + "errors" + + "github.com/hyperledger/fabric/core/chaincode/shim" + "github.com/hyperledger/fabric/protos/peer" + "github.com/s7techlab/cckit/gateway/service" +) + +func ResponseError(in *service.ChaincodeExec, r peer.Response) (peer.Response, error) { + return peer.Response{ + Status: shim.ERROR, + }, errors.New(`peer is down`) +} + +func ResponseInvokeError(in *service.ChaincodeExec, r peer.Response) (peer.Response, error) { + if in.Type == service.InvocationType_INVOKE { + return peer.Response{ + Status: shim.ERROR, + }, errors.New(`peer is down`) + } + + return r, nil +} diff --git a/gateway/service/service_test.go b/gateway/service/service_test.go new file mode 100644 index 00000000..823b48f9 --- /dev/null +++ b/gateway/service/service_test.go @@ -0,0 +1,61 @@ +package service_test + +import ( + "context" + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "github.com/golang/protobuf/ptypes/empty" + "github.com/s7techlab/cckit/examples/cpaper_asservice" + cpservice "github.com/s7techlab/cckit/examples/cpaper_asservice/service" + "github.com/s7techlab/cckit/gateway/service" + "github.com/s7techlab/cckit/gateway/service/mock" + "github.com/s7techlab/cckit/identity/testdata" + testcc "github.com/s7techlab/cckit/testing" +) + +func TestService(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Mockstub Suite") +} + +const ( + Channel = `my_channel` + ChaincodeName = `commercial_paper` +) + +var ( + cPaperService *mock.ChaincodeService + cPaperGateway *cpservice.CPaperGateway + ctx = service.ContextWithSigner(context.Background(), testdata.Identities[0]) +) + +var _ = Describe(`Service`, func() { + + It("Init", func() { + + ccImpl, err := cpaper_asservice.NewCC() + Expect(err).NotTo(HaveOccurred()) + + // peer imitation + cPaperService = mock.New().WithChannel(Channel, testcc.NewMockStub(ChaincodeName, ccImpl)) + + // "sdk" for deal with cpaper chaincode + cPaperGateway = cpservice.NewCPaperGateway(cPaperService, Channel, ChaincodeName) + }) + + It("Allow to get empty commercial paper list", func() { + pp, err := cPaperGateway.List(ctx, &empty.Empty{}) + Expect(err).NotTo(HaveOccurred()) + Expect(pp.Items).To(HaveLen(0)) + }) + + It("Allow to imitate error while access to peer", func() { + cPaperService.ResponseMutator = mock.ResponseError + + _, err := cPaperGateway.List(ctx, &empty.Empty{}) + Expect(err).To(HaveOccurred()) + }) +}) diff --git a/identity/testdata/testdata.go b/identity/testdata/testdata.go index 6c68bd36..3b749f9b 100644 --- a/identity/testdata/testdata.go +++ b/identity/testdata/testdata.go @@ -8,9 +8,8 @@ import ( "path" "runtime" - "github.com/s7techlab/cckit/testing" - "github.com/s7techlab/cckit/identity" + "github.com/s7techlab/cckit/testing" ) const DefaultMSP = `SOME_MSP` diff --git a/testing/mockstub.go b/testing/mockstub.go index 3f6ba121..d6ed75a3 100644 --- a/testing/mockstub.go +++ b/testing/mockstub.go @@ -5,6 +5,7 @@ import ( "crypto/rand" "fmt" "strings" + "sync" "unicode/utf8" "github.com/hyperledger/fabric/core/chaincode/shim" @@ -31,6 +32,7 @@ var ( type MockStub struct { shim.MockStub cc shim.Chaincode + m sync.Mutex mockCreator []byte transient map[string][]byte ClearCreatorAfterInvoke bool @@ -128,7 +130,7 @@ func (stub *MockStub) InvokeChaincode(chaincodeName string, args [][]byte, chann otherStub, exists := stub.InvokablesFull[chaincodeName] if !exists { return shim.Error(fmt.Sprintf( - `%s: try to invoke chaincode "%s" in channel "%s" (%s). Available mocked chaincodes are: %s`, + `%s : try to invoke chaincode "%s" in channel "%s" (%s). Available mocked chaincodes are: %s`, ErrChaincodeNotExists, ccName, channel, chaincodeName, stub.MockedPeerChaincodes())) } @@ -204,6 +206,9 @@ func (stub *MockStub) MockQuery(uuid string, args [][]byte) peer.Response { // MockInvoke func (stub *MockStub) MockInvoke(uuid string, args [][]byte) peer.Response { + stub.m.Lock() + defer stub.m.Unlock() + // this is a hack here to set MockStub.args, because its not accessible otherwise stub.SetArgs(args)