From a7e8cb0b7e77f1e9504727fce2b7ad6307379168 Mon Sep 17 00:00:00 2001 From: yacovm Date: Sun, 22 Apr 2018 22:12:43 +0300 Subject: [PATCH] [FAB-8532] Connect plugin endorser to the endorser This change set connects the pluggable endorsement to the endorser Change-Id: Icd46fb4852227ad4e1304518733a370d97c80b80 Signed-off-by: yacovm --- core/endorser/endorser.go | 94 ++--- core/endorser/endorser_test.go | 152 +++++-- core/endorser/java.go | 2 +- core/endorser/nojava.go | 2 +- core/endorser/plugin_endorser.go | 18 +- core/endorser/plugin_endorser_test.go | 97 +++-- core/endorser/state.go | 2 +- core/endorser/support.go | 26 +- .../builtin/default_endorsement.go | 67 +++ .../builtin/default_endorsement_test.go | 63 +++ core/handlers/endorsement/builtin/escc.go | 57 --- .../builtin/mocks/signing_identity.go | 61 +++ .../plugin/{escc_plugin.go => plugin.go} | 30 +- core/handlers/library/library.go | 4 +- core/mocks/endorser/support.go | 22 + core/scc/escc/endorser_onevalidsignature.go | 180 -------- .../escc/endorser_onevalidsignature_test.go | 386 ------------------ core/scc/importsysccs.go | 8 - core/scc/lscc/lscc.go | 4 - core/scc/lscc/lscc_test.go | 9 - peer/node/start.go | 38 +- sampleconfig/core.yaml | 15 +- 22 files changed, 511 insertions(+), 826 deletions(-) create mode 100644 core/handlers/endorsement/builtin/default_endorsement.go create mode 100644 core/handlers/endorsement/builtin/default_endorsement_test.go delete mode 100644 core/handlers/endorsement/builtin/escc.go create mode 100644 core/handlers/endorsement/builtin/mocks/signing_identity.go rename core/handlers/endorsement/plugin/{escc_plugin.go => plugin.go} (66%) delete mode 100644 core/scc/escc/endorser_onevalidsignature.go delete mode 100644 core/scc/escc/endorser_onevalidsignature_test.go diff --git a/core/endorser/endorser.go b/core/endorser/endorser.go index 31ea232d305..68ed787fdf9 100644 --- a/core/endorser/endorser.go +++ b/core/endorser/endorser.go @@ -10,6 +10,7 @@ import ( "fmt" "github.com/hyperledger/fabric/common/channelconfig" + "github.com/hyperledger/fabric/common/crypto" "github.com/hyperledger/fabric/common/flogging" "github.com/hyperledger/fabric/common/util" "github.com/hyperledger/fabric/core/chaincode" @@ -34,6 +35,7 @@ type privateDataDistributor func(channel string, txID string, privateData *rwset // Support contains functions that the endorser requires to execute its tasks type Support interface { + crypto.SignerSupport // IsSysCCAndNotInvokableExternal returns true if the supplied chaincode is // ia system chaincode and it NOT invokable IsSysCCAndNotInvokableExternal(name string) bool @@ -60,7 +62,7 @@ type Support interface { // GetChaincodeDefinition returns ccprovider.ChaincodeDefinition for the chaincode with the supplied name GetChaincodeDefinition(ctx context.Context, chainID string, txid string, signedProp *pb.SignedProposal, prop *pb.Proposal, chaincodeID string, txsim ledger.TxSimulator) (ccprovider.ChaincodeDefinition, error) - //CheckACL checks the ACL for the resource for the channel using the + //CheckACL checks the ACL for the resource for the Channel using the //SignedProposal from which an id can be extracted for testing against a policy CheckACL(signedProp *pb.SignedProposal, chdr *common.ChannelHeader, shdr *common.SignatureHeader, hdrext *pb.ChaincodeHeaderExtension) error @@ -72,9 +74,15 @@ type Support interface { // ChaincodeDefinition differs from the instantiation policy stored on the ledger CheckInstantiationPolicy(name, version string, cd ccprovider.ChaincodeDefinition) error - // GetApplicationConfig returns the configtxapplication.SharedConfig for the channel + // GetApplicationConfig returns the configtxapplication.SharedConfig for the Channel // and whether the Application config exists GetApplicationConfig(cid string) (channelconfig.Application, bool) + + // NewQueryCreator creates a new QueryCreator + NewQueryCreator(channel string) (QueryCreator, error) + + // EndorseWithPlugin endorses the response with a plugin + EndorseWithPlugin(ctx Context) (*pb.ProposalResponse, error) } // Endorser provides the Endorser service ProcessProposal @@ -93,7 +101,7 @@ type validateResult struct { } // NewEndorserServer creates and returns a new Endorser server instance. -func NewEndorserServer(privDist privateDataDistributor, s Support) pb.EndorserServer { +func NewEndorserServer(privDist privateDataDistributor, s Support) *Endorser { e := &Endorser{ distributePrivateData: privDist, s: s, @@ -103,8 +111,8 @@ func NewEndorserServer(privDist privateDataDistributor, s Support) pb.EndorserSe //call specified chaincode (system or user) func (e *Endorser) callChaincode(ctxt context.Context, chainID string, version string, txid string, signedProp *pb.SignedProposal, prop *pb.Proposal, cis *pb.ChaincodeInvocationSpec, cid *pb.ChaincodeID, txsim ledger.TxSimulator) (*pb.Response, *pb.ChaincodeEvent, error) { - endorserLogger.Debugf("[%s][%s] Entry chaincode: %s version: %s", chainID, shorttxid(txid), cid, version) - defer endorserLogger.Debugf("[%s][%s] Exit", chainID, shorttxid(txid)) + endorserLogger.Debugf("[%s][%s] Entry chaincode: %s version: %s", chainID, txid, cid, version) + defer endorserLogger.Debugf("[%s][%s] Exit", chainID, txid) var err error var res *pb.Response var ccevent *pb.ChaincodeEvent @@ -160,7 +168,7 @@ func (e *Endorser) callChaincode(ctxt context.Context, chainID string, version s //TO BE REMOVED WHEN JAVA CC IS ENABLED //disableJavaCCInst if trying to install, instantiate or upgrade Java CC -func (e *Endorser) disableJavaCCInst(cid *pb.ChaincodeID, cis *pb.ChaincodeInvocationSpec) error { +func (e *Endorser) DisableJavaCCInst(cid *pb.ChaincodeID, cis *pb.ChaincodeInvocationSpec) error { //if not lscc we don't care if cid.Name != "lscc" { return nil @@ -191,7 +199,7 @@ func (e *Endorser) disableJavaCCInst(cid *pb.ChaincodeID, cis *pb.ChaincodeInvoc return errors.Errorf("too few arguments passed. expected %d", argNo) } - if javaEnabled() { + if JavaEnabled() { endorserLogger.Debug("java chaincode enabled") } else { endorserLogger.Debug("java chaincode disabled") @@ -209,8 +217,8 @@ func (e *Endorser) disableJavaCCInst(cid *pb.ChaincodeID, cis *pb.ChaincodeInvoc return nil } -//simulate the proposal by calling the chaincode -func (e *Endorser) simulateProposal(ctx context.Context, chainID string, txid string, signedProp *pb.SignedProposal, prop *pb.Proposal, cid *pb.ChaincodeID, txsim ledger.TxSimulator) (ccprovider.ChaincodeDefinition, *pb.Response, []byte, *pb.ChaincodeEvent, error) { +//SimulateProposal simulates the proposal by calling the chaincode +func (e *Endorser) SimulateProposal(ctx context.Context, chainID string, txid string, signedProp *pb.SignedProposal, prop *pb.Proposal, cid *pb.ChaincodeID, txsim ledger.TxSimulator) (ccprovider.ChaincodeDefinition, *pb.Response, []byte, *pb.ChaincodeEvent, error) { endorserLogger.Debugf("[%s][%s] Entry chaincode: %s", chainID, shorttxid(txid), cid) defer endorserLogger.Debugf("[%s][%s] Exit", chainID, shorttxid(txid)) //we do expect the payload to be a ChaincodeInvocationSpec @@ -222,7 +230,7 @@ func (e *Endorser) simulateProposal(ctx context.Context, chainID string, txid st } //disable Java install,instantiate,upgrade for now - if err = e.disableJavaCCInst(cid, cis); err != nil { + if err = e.DisableJavaCCInst(cid, cis); err != nil { return nil, nil, nil, nil, err } @@ -281,7 +289,7 @@ func (e *Endorser) simulateProposal(ctx context.Context, chainID string, txid st } //endorse the proposal by calling the ESCC -func (e *Endorser) endorseProposal(ctx context.Context, chainID string, txid string, signedProp *pb.SignedProposal, proposal *pb.Proposal, response *pb.Response, simRes []byte, event *pb.ChaincodeEvent, visibility []byte, ccid *pb.ChaincodeID, txsim ledger.TxSimulator, cd ccprovider.ChaincodeDefinition) (*pb.ProposalResponse, error) { +func (e *Endorser) endorseProposal(_ context.Context, chainID string, txid string, signedProp *pb.SignedProposal, proposal *pb.Proposal, response *pb.Response, simRes []byte, event *pb.ChaincodeEvent, visibility []byte, ccid *pb.ChaincodeID, txsim ledger.TxSimulator, cd ccprovider.ChaincodeDefinition) (*pb.ProposalResponse, error) { endorserLogger.Debugf("[%s][%s] Entry chaincode: %s", chainID, shorttxid(txid), ccid) defer endorserLogger.Debugf("[%s][%s] Exit", chainID, shorttxid(txid)) @@ -307,11 +315,6 @@ func (e *Endorser) endorseProposal(ctx context.Context, chainID string, txid str } } - resBytes, err := putils.GetBytesResponse(response) - if err != nil { - return nil, errors.Wrap(err, "failed to marshal response bytes") - } - // set version of executing chaincode if isSysCC { // if we want to allow mixed fabric levels we should @@ -321,50 +324,19 @@ func (e *Endorser) endorseProposal(ctx context.Context, chainID string, txid str ccid.Version = cd.CCVersion() } - ccidBytes, err := putils.Marshal(ccid) - if err != nil { - return nil, errors.Wrap(err, "failed to marshal ChaincodeID") - } - - // 3) call the ESCC we've identified - // arguments: - // args[0] - function name (not used now) - // args[1] - serialized Header object - // args[2] - serialized ChaincodeProposalPayload object - // args[3] - ChaincodeID of executing chaincode - // args[4] - result of executing chaincode - // args[5] - binary blob of simulation results - // args[6] - serialized events - // args[7] - payloadVisibility - args := [][]byte{[]byte(""), proposal.Header, proposal.Payload, ccidBytes, resBytes, simRes, eventBytes, visibility} - version := util.GetSysCCVersion() - ecccis := &pb.ChaincodeInvocationSpec{ChaincodeSpec: &pb.ChaincodeSpec{Type: pb.ChaincodeSpec_GOLANG, ChaincodeId: &pb.ChaincodeID{Name: escc}, Input: &pb.ChaincodeInput{Args: args}}} - res, _, err := e.callChaincode(ctx, chainID, version, txid, signedProp, proposal, ecccis, &pb.ChaincodeID{Name: escc}, txsim) - if err != nil { - return nil, err + ctx := Context{ + PluginName: escc, + Channel: chainID, + SignedProposal: signedProp, + ChaincodeID: ccid, + Event: eventBytes, + SimRes: simRes, + Response: response, + Visibility: visibility, + Proposal: proposal, + TxID: txid, } - - if res.Status >= shim.ERRORTHRESHOLD { - return &pb.ProposalResponse{Response: res}, nil - } - - prBytes := res.Payload - // Note that we do not extract any simulation results from - // the call to ESCC. This is intentional becuse ESCC is meant - // to endorse (i.e. sign) the simulation results of a chaincode, - // but it can't obviously sign its own. Furthermore, ESCC runs - // on private input (its own signing key) and so if it were to - // produce simulationr results, they are likely to be different - // from other ESCCs, which would stand in the way of the - // endorsement process. - - //3 -- respond - pResp, err := putils.GetProposalResponse(prBytes) - if err != nil { - return nil, err - } - - return pResp, nil + return e.s.EndorseWithPlugin(ctx) } //preProcess checks the tx proposal headers, uniqueness and ACL @@ -422,7 +394,7 @@ func (e *Endorser) preProcess(signedProp *pb.SignedProposal) (*validateResult, e // check ACL only for application chaincodes; ACLs // for system chaincodes are checked elsewhere if !e.s.IsSysCC(hdrExt.ChaincodeId.Name) { - // check that the proposal complies with the channel's writers + // check that the proposal complies with the Channel's writers if err = e.s.CheckACL(signedProp, chdr, shdr, hdrExt); err != nil { vr.resp = &pb.ProposalResponse{Response: &pb.Response{Status: 500, Message: err.Error()}} return vr, err @@ -488,7 +460,7 @@ func (e *Endorser) ProcessProposal(ctx context.Context, signedProp *pb.SignedPro // to validate the supplied action before endorsing it //1 -- simulate - cd, res, simulationResult, ccevent, err := e.simulateProposal(ctx, chainID, txid, signedProp, prop, hdrExt.ChaincodeId, txsim) + cd, res, simulationResult, ccevent, err := e.SimulateProposal(ctx, chainID, txid, signedProp, prop, hdrExt.ChaincodeId, txsim) if err != nil { return &pb.ProposalResponse{Response: &pb.Response{Status: 500, Message: err.Error()}}, nil } diff --git a/core/endorser/endorser_test.go b/core/endorser/endorser_test.go index 073dbea08fb..1237cf63ffd 100644 --- a/core/endorser/endorser_test.go +++ b/core/endorser/endorser_test.go @@ -4,7 +4,7 @@ Copyright IBM Corp. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ -package endorser +package endorser_test import ( "context" @@ -13,8 +13,12 @@ import ( "testing" mc "github.com/hyperledger/fabric/common/mocks/config" + "github.com/hyperledger/fabric/common/mocks/resourcesconfig" "github.com/hyperledger/fabric/common/util" "github.com/hyperledger/fabric/core/common/ccprovider" + "github.com/hyperledger/fabric/core/endorser" + "github.com/hyperledger/fabric/core/endorser/mocks" + "github.com/hyperledger/fabric/core/handlers/endorsement/builtin" "github.com/hyperledger/fabric/core/ledger" mockccprovider "github.com/hyperledger/fabric/core/mocks/ccprovider" em "github.com/hyperledger/fabric/core/mocks/endorser" @@ -27,6 +31,7 @@ import ( "github.com/hyperledger/fabric/protos/utils" "github.com/pkg/errors" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" ) func getSignedPropWithCHID(ccid, ccver, chid string, t *testing.T) *pb.SignedProposal { @@ -56,7 +61,7 @@ func getSignedPropWithCHIdAndArgs(chid, ccid, ccver string, ccargs [][]byte, t * } func TestEndorserNilProp(t *testing.T) { - es := NewEndorserServer(func(channel string, txID string, privateData *rwset.TxPvtReadWriteSet, blkHt uint64) error { + es := endorser.NewEndorserServer(func(channel string, txID string, privateData *rwset.TxPvtReadWriteSet, blkHt uint64) error { return nil }, &em.MockSupport{ GetApplicationConfigBoolRv: true, @@ -74,7 +79,7 @@ func TestEndorserNilProp(t *testing.T) { } func TestEndorserUninvokableSysCC(t *testing.T) { - es := NewEndorserServer(func(channel string, txID string, privateData *rwset.TxPvtReadWriteSet, blkHt uint64) error { + es := endorser.NewEndorserServer(func(channel string, txID string, privateData *rwset.TxPvtReadWriteSet, blkHt uint64) error { return nil }, &em.MockSupport{ GetApplicationConfigBoolRv: true, @@ -92,7 +97,7 @@ func TestEndorserUninvokableSysCC(t *testing.T) { } func TestEndorserCCInvocationFailed(t *testing.T) { - es := NewEndorserServer(func(channel string, txID string, privateData *rwset.TxPvtReadWriteSet, blkHt uint64) error { + es := endorser.NewEndorserServer(func(channel string, txID string, privateData *rwset.TxPvtReadWriteSet, blkHt uint64) error { return nil }, &em.MockSupport{ GetApplicationConfigBoolRv: true, @@ -112,7 +117,7 @@ func TestEndorserCCInvocationFailed(t *testing.T) { } func TestEndorserNoCCDef(t *testing.T) { - es := NewEndorserServer(func(channel string, txID string, privateData *rwset.TxPvtReadWriteSet, blkHt uint64) error { + es := endorser.NewEndorserServer(func(channel string, txID string, privateData *rwset.TxPvtReadWriteSet, blkHt uint64) error { return nil }, &em.MockSupport{ GetApplicationConfigBoolRv: true, @@ -132,7 +137,7 @@ func TestEndorserNoCCDef(t *testing.T) { } func TestEndorserBadInstPolicy(t *testing.T) { - es := NewEndorserServer(func(channel string, txID string, privateData *rwset.TxPvtReadWriteSet, blkHt uint64) error { + es := endorser.NewEndorserServer(func(channel string, txID string, privateData *rwset.TxPvtReadWriteSet, blkHt uint64) error { return nil }, &em.MockSupport{ GetApplicationConfigBoolRv: true, @@ -152,9 +157,11 @@ func TestEndorserBadInstPolicy(t *testing.T) { } func TestEndorserSysCC(t *testing.T) { - es := NewEndorserServer(func(channel string, txID string, privateData *rwset.TxPvtReadWriteSet, blkHt uint64) error { - return nil - }, &em.MockSupport{ + m := &mock.Mock{} + m.On("Sign", mock.Anything).Return([]byte{1, 2, 3, 4, 5}, nil) + m.On("Serialize").Return([]byte{1, 1, 1}, nil) + support := &em.MockSupport{ + Mock: m, GetApplicationConfigBoolRv: true, GetApplicationConfigRv: &mc.MockApplication{CapabilitiesRv: &mc.MockApplicationCapabilities{}}, GetTransactionByIDErr: errors.New(""), @@ -162,7 +169,11 @@ func TestEndorserSysCC(t *testing.T) { ChaincodeDefinitionRv: &ccprovider.ChaincodeData{Escc: "ESCC"}, ExecuteResp: &pb.Response{Status: 200, Payload: utils.MarshalOrPanic(&pb.ProposalResponse{Response: &pb.Response{}})}, GetTxSimulatorRv: &mockccprovider.MockTxSim{&ledger.TxSimulationResults{PubSimulationResults: &rwset.TxReadWriteSet{}}}, - }) + } + attachPluginEndorser(support) + es := endorser.NewEndorserServer(func(channel string, txID string, privateData *rwset.TxPvtReadWriteSet, blkHt uint64) error { + return nil + }, support) signedProp := getSignedProp("ccid", "0", t) @@ -172,7 +183,7 @@ func TestEndorserSysCC(t *testing.T) { } func TestEndorserCCInvocationError(t *testing.T) { - es := NewEndorserServer(func(channel string, txID string, privateData *rwset.TxPvtReadWriteSet, blkHt uint64) error { + es := endorser.NewEndorserServer(func(channel string, txID string, privateData *rwset.TxPvtReadWriteSet, blkHt uint64) error { return nil }, &em.MockSupport{ GetApplicationConfigBoolRv: true, @@ -191,7 +202,7 @@ func TestEndorserCCInvocationError(t *testing.T) { } func TestEndorserLSCCBadType(t *testing.T) { - es := NewEndorserServer(func(channel string, txID string, privateData *rwset.TxPvtReadWriteSet, blkHt uint64) error { + es := endorser.NewEndorserServer(func(channel string, txID string, privateData *rwset.TxPvtReadWriteSet, blkHt uint64) error { return nil }, &em.MockSupport{ GetApplicationConfigBoolRv: true, @@ -219,7 +230,7 @@ func TestEndorserLSCCBadType(t *testing.T) { } func TestEndorserDupTXId(t *testing.T) { - es := NewEndorserServer(func(channel string, txID string, privateData *rwset.TxPvtReadWriteSet, blkHt uint64) error { + es := endorser.NewEndorserServer(func(channel string, txID string, privateData *rwset.TxPvtReadWriteSet, blkHt uint64) error { return nil }, &em.MockSupport{ GetApplicationConfigBoolRv: true, @@ -238,7 +249,7 @@ func TestEndorserDupTXId(t *testing.T) { } func TestEndorserBadACL(t *testing.T) { - es := NewEndorserServer(func(channel string, txID string, privateData *rwset.TxPvtReadWriteSet, blkHt uint64) error { + es := endorser.NewEndorserServer(func(channel string, txID string, privateData *rwset.TxPvtReadWriteSet, blkHt uint64) error { return nil }, &em.MockSupport{ GetApplicationConfigBoolRv: true, @@ -258,7 +269,7 @@ func TestEndorserBadACL(t *testing.T) { } func TestEndorserGoodPathEmptyChannel(t *testing.T) { - es := NewEndorserServer(func(channel string, txID string, privateData *rwset.TxPvtReadWriteSet, blkHt uint64) error { + es := endorser.NewEndorserServer(func(channel string, txID string, privateData *rwset.TxPvtReadWriteSet, blkHt uint64) error { return nil }, &em.MockSupport{ GetApplicationConfigBoolRv: true, @@ -277,7 +288,7 @@ func TestEndorserGoodPathEmptyChannel(t *testing.T) { } func TestEndorserLSCCInitFails(t *testing.T) { - es := NewEndorserServer(func(channel string, txID string, privateData *rwset.TxPvtReadWriteSet, blkHt uint64) error { + es := endorser.NewEndorserServer(func(channel string, txID string, privateData *rwset.TxPvtReadWriteSet, blkHt uint64) error { return nil }, &em.MockSupport{ GetApplicationConfigBoolRv: true, @@ -308,7 +319,7 @@ func TestEndorserLSCCDeploySysCC(t *testing.T) { SysCCMap := make(map[string]struct{}) deployedCCName := "barf" SysCCMap[deployedCCName] = struct{}{} - es := NewEndorserServer(func(channel string, txID string, privateData *rwset.TxPvtReadWriteSet, blkHt uint64) error { + es := endorser.NewEndorserServer(func(channel string, txID string, privateData *rwset.TxPvtReadWriteSet, blkHt uint64) error { return nil }, &em.MockSupport{ GetApplicationConfigBoolRv: true, @@ -337,11 +348,11 @@ func TestEndorserLSCCDeploySysCC(t *testing.T) { } func TestEndorserLSCCJava1(t *testing.T) { - if javaEnabled() { + if endorser.JavaEnabled() { t.Skip("Java chaincode is supported") } - es := NewEndorserServer(func(channel string, txID string, privateData *rwset.TxPvtReadWriteSet, blkHt uint64) error { + es := endorser.NewEndorserServer(func(channel string, txID string, privateData *rwset.TxPvtReadWriteSet, blkHt uint64) error { return nil }, &em.MockSupport{ GetApplicationConfigBoolRv: true, @@ -370,11 +381,11 @@ func TestEndorserLSCCJava1(t *testing.T) { } func TestEndorserLSCCJava2(t *testing.T) { - if javaEnabled() { + if endorser.JavaEnabled() { t.Skip("Java chaincode is supported") } - es := NewEndorserServer(func(channel string, txID string, privateData *rwset.TxPvtReadWriteSet, blkHt uint64) error { + es := endorser.NewEndorserServer(func(channel string, txID string, privateData *rwset.TxPvtReadWriteSet, blkHt uint64) error { return nil }, &em.MockSupport{ GetApplicationConfigBoolRv: true, @@ -402,9 +413,11 @@ func TestEndorserLSCCJava2(t *testing.T) { } func TestEndorserGoodPathWEvents(t *testing.T) { - es := NewEndorserServer(func(channel string, txID string, privateData *rwset.TxPvtReadWriteSet, blkHt uint64) error { - return nil - }, &em.MockSupport{ + m := &mock.Mock{} + m.On("Sign", mock.Anything).Return([]byte{1, 2, 3, 4, 5}, nil) + m.On("Serialize").Return([]byte{1, 1, 1}, nil) + support := &em.MockSupport{ + Mock: m, GetApplicationConfigBoolRv: true, GetApplicationConfigRv: &mc.MockApplication{CapabilitiesRv: &mc.MockApplicationCapabilities{}}, GetTransactionByIDErr: errors.New(""), @@ -412,7 +425,11 @@ func TestEndorserGoodPathWEvents(t *testing.T) { ExecuteResp: &pb.Response{Status: 200, Payload: utils.MarshalOrPanic(&pb.ProposalResponse{Response: &pb.Response{}})}, ExecuteEvent: &pb.ChaincodeEvent{}, GetTxSimulatorRv: &mockccprovider.MockTxSim{&ledger.TxSimulationResults{PubSimulationResults: &rwset.TxReadWriteSet{}}}, - }) + } + attachPluginEndorser(support) + es := endorser.NewEndorserServer(func(channel string, txID string, privateData *rwset.TxPvtReadWriteSet, blkHt uint64) error { + return nil + }, support) signedProp := getSignedProp("ccid", "0", t) @@ -422,7 +439,7 @@ func TestEndorserGoodPathWEvents(t *testing.T) { } func TestEndorserBadChannel(t *testing.T) { - es := NewEndorserServer(func(channel string, txID string, privateData *rwset.TxPvtReadWriteSet, blkHt uint64) error { + es := endorser.NewEndorserServer(func(channel string, txID string, privateData *rwset.TxPvtReadWriteSet, blkHt uint64) error { return nil }, &em.MockSupport{ GetApplicationConfigBoolRv: true, @@ -442,16 +459,22 @@ func TestEndorserBadChannel(t *testing.T) { } func TestEndorserGoodPath(t *testing.T) { - es := NewEndorserServer(func(channel string, txID string, privateData *rwset.TxPvtReadWriteSet, blkHt uint64) error { - return nil - }, &em.MockSupport{ + m := &mock.Mock{} + m.On("Sign", mock.Anything).Return([]byte{1, 2, 3, 4, 5}, nil) + m.On("Serialize").Return([]byte{1, 1, 1}, nil) + support := &em.MockSupport{ + Mock: m, GetApplicationConfigBoolRv: true, GetApplicationConfigRv: &mc.MockApplication{CapabilitiesRv: &mc.MockApplicationCapabilities{}}, GetTransactionByIDErr: errors.New(""), ChaincodeDefinitionRv: &ccprovider.ChaincodeData{Escc: "ESCC"}, ExecuteResp: &pb.Response{Status: 200, Payload: utils.MarshalOrPanic(&pb.ProposalResponse{Response: &pb.Response{}})}, GetTxSimulatorRv: &mockccprovider.MockTxSim{&ledger.TxSimulationResults{PubSimulationResults: &rwset.TxReadWriteSet{}}}, - }) + } + attachPluginEndorser(support) + es := endorser.NewEndorserServer(func(channel string, txID string, privateData *rwset.TxPvtReadWriteSet, blkHt uint64) error { + return nil + }, support) signedProp := getSignedProp("ccid", "0", t) @@ -461,16 +484,22 @@ func TestEndorserGoodPath(t *testing.T) { } func TestEndorserLSCC(t *testing.T) { - es := NewEndorserServer(func(channel string, txID string, privateData *rwset.TxPvtReadWriteSet, blkHt uint64) error { - return nil - }, &em.MockSupport{ + m := &mock.Mock{} + m.On("Sign", mock.Anything).Return([]byte{1, 2, 3, 4, 5}, nil) + m.On("Serialize").Return([]byte{1, 1, 1}, nil) + support := &em.MockSupport{ + Mock: m, GetApplicationConfigBoolRv: true, GetApplicationConfigRv: &mc.MockApplication{CapabilitiesRv: &mc.MockApplicationCapabilities{}}, GetTransactionByIDErr: errors.New(""), ChaincodeDefinitionRv: &ccprovider.ChaincodeData{Escc: "ESCC"}, ExecuteResp: &pb.Response{Status: 200, Payload: utils.MarshalOrPanic(&pb.ProposalResponse{Response: &pb.Response{}})}, GetTxSimulatorRv: &mockccprovider.MockTxSim{&ledger.TxSimulationResults{PubSimulationResults: &rwset.TxReadWriteSet{}}}, - }) + } + attachPluginEndorser(support) + es := endorser.NewEndorserServer(func(channel string, txID string, privateData *rwset.TxPvtReadWriteSet, blkHt uint64) error { + return nil + }, support) cds := utils.MarshalOrPanic( &pb.ChaincodeDeploymentSpec{ @@ -487,8 +516,47 @@ func TestEndorserLSCC(t *testing.T) { assert.EqualValues(t, 200, pResp.Response.Status) } +func attachPluginEndorser(support *em.MockSupport) { + csr := &mocks.ChannelStateRetriever{} + queryCreator := &mocks.QueryCreator{} + csr.On("NewQueryCreator", mock.Anything).Return(queryCreator, nil) + sif := &mocks.SigningIdentityFetcher{} + sif.On("SigningIdentityForRequest", mock.Anything).Return(support, nil) + pm := &mocks.PluginMapper{} + pm.On("PluginFactoryByName", mock.Anything).Return(&builtin.DefaultEndorsementFactory{}) + support.PluginEndorser = endorser.NewPluginEndorser(csr, sif, pm) +} + +func TestEndorseWithPlugin(t *testing.T) { + m := &mock.Mock{} + m.On("Sign", mock.Anything).Return([]byte{1, 2, 3, 4, 5}, nil) + m.On("Serialize").Return([]byte{1, 1, 1}, nil) + support := &em.MockSupport{ + Mock: m, + GetApplicationConfigBoolRv: true, + GetApplicationConfigRv: &mc.MockApplication{CapabilitiesRv: &mc.MockApplicationCapabilities{}}, + GetTransactionByIDErr: errors.New("can't find this transaction in the index"), + ChaincodeDefinitionRv: &resourceconfig.MockChaincodeDefinition{EndorsementStr: "ESCC"}, + ExecuteResp: &pb.Response{Status: 200, Payload: []byte{1}}, + GetTxSimulatorRv: &mockccprovider.MockTxSim{&ledger.TxSimulationResults{PubSimulationResults: &rwset.TxReadWriteSet{}}}, + } + attachPluginEndorser(support) + + es := endorser.NewEndorserServer(func(_ string, _ string, _ *rwset.TxPvtReadWriteSet, _ uint64) error { + return nil + }, support) + + signedProp := getSignedProp("ccid", "0", t) + + resp, err := es.ProcessProposal(context.Background(), signedProp) + assert.NoError(t, err) + assert.Equal(t, []byte{1, 2, 3, 4, 5}, resp.Endorsement.Signature) + assert.Equal(t, []byte{1, 1, 1}, resp.Endorsement.Endorser) + assert.Equal(t, 200, int(resp.Response.Status)) +} + func TestSimulateProposal(t *testing.T) { - es := NewEndorserServer(func(channel string, txID string, privateData *rwset.TxPvtReadWriteSet, blkHt uint64) error { + es := endorser.NewEndorserServer(func(channel string, txID string, privateData *rwset.TxPvtReadWriteSet, blkHt uint64) error { return nil }, &em.MockSupport{ GetApplicationConfigBoolRv: true, @@ -499,16 +567,16 @@ func TestSimulateProposal(t *testing.T) { GetTxSimulatorRv: &mockccprovider.MockTxSim{&ledger.TxSimulationResults{PubSimulationResults: &rwset.TxReadWriteSet{}}}, }) - _, _, _, _, err := es.(*Endorser).simulateProposal(nil, "", "", nil, nil, nil, nil) + _, _, _, _, err := es.SimulateProposal(nil, "", "", nil, nil, nil, nil) assert.Error(t, err) } func TestEndorserJavaChecks(t *testing.T) { - if javaEnabled() { + if endorser.JavaEnabled() { t.Skip("Java chaincode is supported") } - es := NewEndorserServer(func(channel string, txID string, privateData *rwset.TxPvtReadWriteSet, blkHt uint64) error { + es := endorser.NewEndorserServer(func(channel string, txID string, privateData *rwset.TxPvtReadWriteSet, blkHt uint64) error { return nil }, &em.MockSupport{ GetApplicationConfigBoolRv: true, @@ -519,13 +587,13 @@ func TestEndorserJavaChecks(t *testing.T) { GetTxSimulatorRv: &mockccprovider.MockTxSim{&ledger.TxSimulationResults{PubSimulationResults: &rwset.TxReadWriteSet{}}}, }) - err := es.(*Endorser).disableJavaCCInst(&pb.ChaincodeID{Name: "lscc"}, &pb.ChaincodeInvocationSpec{}) + err := es.DisableJavaCCInst(&pb.ChaincodeID{Name: "lscc"}, &pb.ChaincodeInvocationSpec{}) assert.NoError(t, err) - err = es.(*Endorser).disableJavaCCInst(&pb.ChaincodeID{Name: "lscc"}, &pb.ChaincodeInvocationSpec{ChaincodeSpec: &pb.ChaincodeSpec{Input: &pb.ChaincodeInput{}}}) + err = es.DisableJavaCCInst(&pb.ChaincodeID{Name: "lscc"}, &pb.ChaincodeInvocationSpec{ChaincodeSpec: &pb.ChaincodeSpec{Input: &pb.ChaincodeInput{}}}) assert.NoError(t, err) - err = es.(*Endorser).disableJavaCCInst(&pb.ChaincodeID{Name: "lscc"}, &pb.ChaincodeInvocationSpec{ChaincodeSpec: &pb.ChaincodeSpec{Input: &pb.ChaincodeInput{Args: [][]byte{[]byte("foo")}}}}) + err = es.DisableJavaCCInst(&pb.ChaincodeID{Name: "lscc"}, &pb.ChaincodeInvocationSpec{ChaincodeSpec: &pb.ChaincodeSpec{Input: &pb.ChaincodeInput{Args: [][]byte{[]byte("foo")}}}}) assert.NoError(t, err) - err = es.(*Endorser).disableJavaCCInst(&pb.ChaincodeID{Name: "lscc"}, &pb.ChaincodeInvocationSpec{ChaincodeSpec: &pb.ChaincodeSpec{Input: &pb.ChaincodeInput{Args: [][]byte{[]byte("install")}}}}) + err = es.DisableJavaCCInst(&pb.ChaincodeID{Name: "lscc"}, &pb.ChaincodeInvocationSpec{ChaincodeSpec: &pb.ChaincodeSpec{Input: &pb.ChaincodeInput{Args: [][]byte{[]byte("install")}}}}) assert.Error(t, err) } diff --git a/core/endorser/java.go b/core/endorser/java.go index 4bd386ae192..99192294214 100644 --- a/core/endorser/java.go +++ b/core/endorser/java.go @@ -8,6 +8,6 @@ SPDX-License-Identifier: Apache-2.0 package endorser -func javaEnabled() bool { +func JavaEnabled() bool { return true } diff --git a/core/endorser/nojava.go b/core/endorser/nojava.go index a1ec62fdc94..605d7a86bc9 100644 --- a/core/endorser/nojava.go +++ b/core/endorser/nojava.go @@ -8,6 +8,6 @@ SPDX-License-Identifier: Apache-2.0 package endorser -func javaEnabled() bool { +func JavaEnabled() bool { return false } diff --git a/core/endorser/plugin_endorser.go b/core/endorser/plugin_endorser.go index ef3c2a3c457..9ec1efc39c6 100644 --- a/core/endorser/plugin_endorser.go +++ b/core/endorser/plugin_endorser.go @@ -1,5 +1,5 @@ /* -Copyright IBM Corp. 2016 All Rights Reserved. +Copyright IBM Corp. 2018 All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ @@ -10,6 +10,7 @@ import ( "fmt" "sync" + "github.com/hyperledger/fabric/core/chaincode/shim" "github.com/hyperledger/fabric/core/handlers/endorsement/api" endorsement3 "github.com/hyperledger/fabric/core/handlers/endorsement/api/identities" pb "github.com/hyperledger/fabric/protos/peer" @@ -130,15 +131,18 @@ type PluginEndorser struct { endorsement3.SigningIdentityFetcher } -// IsEndorsedWithPlugin returns whether there is an endorsement plugin that matches the given name -func (pe *PluginEndorser) IsEndorsedWithPlugin(name string) bool { - return pe.PluginFactoryByName(PluginName(name)) != nil -} - // EndorseWithPlugin endorses the response with a plugin func (pe *PluginEndorser) EndorseWithPlugin(ctx Context) (*pb.ProposalResponse, error) { endorserLogger.Info("Entering endorsement for", ctx) + if ctx.Response == nil { + return nil, errors.New("Response is nil") + } + + if ctx.Response.Status >= shim.ERRORTHRESHOLD { + return &pb.ProposalResponse{Response: ctx.Response}, nil + } + plugin, err := pe.getOrCreatePlugin(PluginName(ctx.PluginName), ctx.Channel) if err != nil { endorserLogger.Warning("Endorsement with plugin for", ctx, " failed:", err) @@ -161,7 +165,7 @@ func (pe *PluginEndorser) EndorseWithPlugin(ctx Context) (*pb.ProposalResponse, Version: 1, Endorsement: endorsement, Payload: prpBytes, - Response: &pb.Response{Status: 200, Message: "OK"}, + Response: ctx.Response, } endorserLogger.Info("Exiting", ctx) return resp, nil diff --git a/core/endorser/plugin_endorser_test.go b/core/endorser/plugin_endorser_test.go index a3b04192f80..9f2adb48918 100644 --- a/core/endorser/plugin_endorser_test.go +++ b/core/endorser/plugin_endorser_test.go @@ -9,6 +9,7 @@ package endorser_test import ( "testing" + "github.com/hyperledger/fabric/core/chaincode/shim" "github.com/hyperledger/fabric/core/endorser" "github.com/hyperledger/fabric/core/endorser/mocks" "github.com/hyperledger/fabric/protos/common" @@ -19,20 +20,12 @@ import ( "github.com/stretchr/testify/mock" ) -func TestIsEndorsedWithPlugin(t *testing.T) { - pluginMapper := &mocks.PluginMapper{} - pluginMapper.On("PluginFactoryByName", endorser.PluginName("existingPluginFactory")).Return(&mocks.PluginFactory{}) - pluginMapper.On("PluginFactoryByName", endorser.PluginName("nonExistingPluginFactory")).Return(nil) - pluginEndorser := endorser.NewPluginEndorser(nil, nil, pluginMapper) - assert.False(t, pluginEndorser.IsEndorsedWithPlugin("nonExistingPluginFactory")) - assert.True(t, pluginEndorser.IsEndorsedWithPlugin("existingPluginFactory")) -} - func TestPluginEndorserNotFound(t *testing.T) { pluginMapper := &mocks.PluginMapper{} pluginMapper.On("PluginFactoryByName", endorser.PluginName("notfound")).Return(nil) pluginEndorser := endorser.NewPluginEndorser(nil, nil, pluginMapper) resp, err := pluginEndorser.EndorseWithPlugin(endorser.Context{ + Response: &peer.Response{}, PluginName: "notfound", }) assert.Nil(t, resp) @@ -62,6 +55,7 @@ func TestPluginEndorserGreenPath(t *testing.T) { cs.On("NewQueryCreator", "mychannel").Return(queryCreator, nil) pluginEndorser := endorser.NewPluginEndorser(cs, sif, pluginMapper) ctx := endorser.Context{ + Response: &peer.Response{}, PluginName: "plugin", Proposal: proposal, ChaincodeID: &peer.ChaincodeID{ @@ -119,32 +113,71 @@ func TestPluginEndorserErrors(t *testing.T) { pluginEndorser := endorser.NewPluginEndorser(cs, sif, pluginMapper) // Scenario I: Failed initializing plugin - plugin.On("Init", mock.Anything, mock.Anything).Return(errors.New("plugin initialization failed")).Once() - resp, err := pluginEndorser.EndorseWithPlugin(endorser.Context{ - PluginName: "plugin", - Channel: "mychannel", + t.Run("PluginInitializationFailure", func(t *testing.T) { + plugin.On("Init", mock.Anything, mock.Anything).Return(errors.New("plugin initialization failed")).Once() + resp, err := pluginEndorser.EndorseWithPlugin(endorser.Context{ + PluginName: "plugin", + Channel: "mychannel", + Response: &peer.Response{}, + }) + assert.Nil(t, resp) + assert.Contains(t, err.Error(), "plugin initialization failed") }) - assert.Nil(t, resp) - assert.Contains(t, err.Error(), "plugin initialization failed") - // Scenario II: an empty proposal is passed in the context, and parsing fails - plugin.On("Init", mock.Anything, mock.Anything).Return(nil).Once() - ctx := endorser.Context{ - PluginName: "plugin", - ChaincodeID: &peer.ChaincodeID{ - Name: "mycc", - }, - Proposal: &peer.Proposal{}, - Channel: "mychannel", - } - resp, err = pluginEndorser.EndorseWithPlugin(ctx) - assert.Nil(t, resp) - assert.Contains(t, err.Error(), "could not compute proposal hash") + t.Run("EmptyProposal", func(t *testing.T) { + plugin.On("Init", mock.Anything, mock.Anything).Return(nil).Once() + ctx := endorser.Context{ + Response: &peer.Response{}, + PluginName: "plugin", + ChaincodeID: &peer.ChaincodeID{ + Name: "mycc", + }, + Proposal: &peer.Proposal{}, + Channel: "mychannel", + } + resp, err := pluginEndorser.EndorseWithPlugin(ctx) + assert.Nil(t, resp) + assert.Contains(t, err.Error(), "could not compute proposal hash") + }) // Scenario III: The proposal's header is invalid - ctx.Proposal.Header = []byte{1, 2, 3} - resp, err = pluginEndorser.EndorseWithPlugin(ctx) - assert.Nil(t, resp) - assert.Contains(t, err.Error(), "failed parsing header") + t.Run("InvalidHeader in the proposal", func(t *testing.T) { + ctx := endorser.Context{ + Response: &peer.Response{}, + PluginName: "plugin", + ChaincodeID: &peer.ChaincodeID{ + Name: "mycc", + }, + Proposal: &peer.Proposal{ + Header: []byte{1, 2, 3}, + }, + Channel: "mychannel", + } + resp, err := pluginEndorser.EndorseWithPlugin(ctx) + assert.Nil(t, resp) + assert.Contains(t, err.Error(), "failed parsing header") + }) + + // Scenario IV: The proposal's response status code indicates an error + t.Run("ResponseStatusContainsError", func(t *testing.T) { + r := &peer.Response{ + Status: shim.ERRORTHRESHOLD, + Payload: []byte{1, 2, 3}, + Message: "bla bla", + } + resp, err := pluginEndorser.EndorseWithPlugin(endorser.Context{ + Response: r, + }) + assert.Equal(t, &peer.ProposalResponse{Response: r}, resp) + assert.NoError(t, err) + }) + + // Scenario V: The proposal's response is nil + t.Run("ResponseIsNil", func(t *testing.T) { + resp, err := pluginEndorser.EndorseWithPlugin(endorser.Context{}) + assert.Nil(t, resp) + assert.Contains(t, err.Error(), "Response is nil") + + }) } diff --git a/core/endorser/state.go b/core/endorser/state.go index b5e0437f3b3..c1d6b769d89 100644 --- a/core/endorser/state.go +++ b/core/endorser/state.go @@ -1,5 +1,5 @@ /* -Copyright IBM Corp. 2016 All Rights Reserved. +Copyright IBM Corp. 2018 All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ diff --git a/core/endorser/support.go b/core/endorser/support.go index ce4f48b3a8d..60a06bddbea 100644 --- a/core/endorser/support.go +++ b/core/endorser/support.go @@ -8,11 +8,13 @@ package endorser import ( "github.com/hyperledger/fabric/common/channelconfig" + "github.com/hyperledger/fabric/common/crypto" "github.com/hyperledger/fabric/core/aclmgmt" "github.com/hyperledger/fabric/core/aclmgmt/resources" "github.com/hyperledger/fabric/core/chaincode" "github.com/hyperledger/fabric/core/common/ccprovider" "github.com/hyperledger/fabric/core/handlers/decoration" + . "github.com/hyperledger/fabric/core/handlers/endorsement/api/identities" "github.com/hyperledger/fabric/core/handlers/library" "github.com/hyperledger/fabric/core/ledger" "github.com/hyperledger/fabric/core/peer" @@ -26,11 +28,25 @@ import ( // SupportImpl provides an implementation of the endorser.Support interface // issuing calls to various static methods of the peer type SupportImpl struct { + *PluginEndorser + crypto.SignerSupport Peer peer.Operations PeerSupport peer.Support ChaincodeSupport *chaincode.ChaincodeSupport } +func (s *SupportImpl) NewQueryCreator(channel string) (QueryCreator, error) { + lgr := s.Peer.GetLedger(channel) + if lgr == nil { + return nil, errors.Errorf("channel %s doesn't exist", channel) + } + return lgr, nil +} + +func (s *SupportImpl) SigningIdentityForRequest(*pb.SignedProposal) (SigningIdentity, error) { + return s.SignerSupport, nil +} + // IsSysCCAndNotInvokableExternal returns true if the supplied chaincode is // ia system chaincode and it NOT invokable func (s *SupportImpl) IsSysCCAndNotInvokableExternal(name string) bool { @@ -43,7 +59,7 @@ func (s *SupportImpl) IsSysCCAndNotInvokableExternal(name string) bool { func (s *SupportImpl) GetTxSimulator(ledgername string, txid string) (ledger.TxSimulator, error) { lgr := s.Peer.GetLedger(ledgername) if lgr == nil { - return nil, errors.Errorf("channel does not exist: %s", ledgername) + return nil, errors.Errorf("Channel does not exist: %s", ledgername) } return lgr.NewTxSimulator(txid) } @@ -53,7 +69,7 @@ func (s *SupportImpl) GetTxSimulator(ledgername string, txid string) (ledger.TxS func (s *SupportImpl) GetHistoryQueryExecutor(ledgername string) (ledger.HistoryQueryExecutor, error) { lgr := s.Peer.GetLedger(ledgername) if lgr == nil { - return nil, errors.Errorf("channel does not exist: %s", ledgername) + return nil, errors.Errorf("Channel does not exist: %s", ledgername) } return lgr.NewHistoryQueryExecutor() } @@ -62,7 +78,7 @@ func (s *SupportImpl) GetHistoryQueryExecutor(ledgername string) (ledger.History func (s *SupportImpl) GetTransactionByID(chid, txID string) (*pb.ProcessedTransaction, error) { lgr := s.Peer.GetLedger(chid) if lgr == nil { - return nil, errors.Errorf("failed to look up the ledger for channel %s", chid) + return nil, errors.Errorf("failed to look up the ledger for Channel %s", chid) } tx, err := lgr.GetTransactionByID(txID) if err != nil { @@ -111,7 +127,7 @@ func (s *SupportImpl) GetChaincodeDefinition(ctx context.Context, chainID string return lifecycle.GetChaincodeDefinition(ctxt, txid, signedProp, prop, chainID, chaincodeID) } -// CheckACL checks the ACL for the resource for the channel using the +// CheckACL checks the ACL for the resource for the Channel using the // SignedProposal from which an id can be extracted for testing against a policy func (s *SupportImpl) CheckACL(signedProp *pb.SignedProposal, chdr *common.ChannelHeader, shdr *common.SignatureHeader, hdrext *pb.ChaincodeHeaderExtension) error { return aclmgmt.GetACLProvider().CheckACL(resources.Peer_Propose, chdr.ChannelId, signedProp) @@ -135,7 +151,7 @@ func (s *SupportImpl) CheckInstantiationPolicy(name, version string, cd ccprovid return ccprovider.CheckInstantiationPolicy(name, version, cd.(*ccprovider.ChaincodeData)) } -// GetApplicationConfig returns the configtxapplication.SharedConfig for the channel +// GetApplicationConfig returns the configtxapplication.SharedConfig for the Channel // and whether the Application config exists func (s *SupportImpl) GetApplicationConfig(cid string) (channelconfig.Application, bool) { return s.PeerSupport.GetApplicationConfig(cid) diff --git a/core/handlers/endorsement/builtin/default_endorsement.go b/core/handlers/endorsement/builtin/default_endorsement.go new file mode 100644 index 00000000000..159411ba76e --- /dev/null +++ b/core/handlers/endorsement/builtin/default_endorsement.go @@ -0,0 +1,67 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package builtin + +import ( + . "github.com/hyperledger/fabric/core/handlers/endorsement/api" + . "github.com/hyperledger/fabric/core/handlers/endorsement/api/identities" + "github.com/hyperledger/fabric/protos/peer" + "github.com/pkg/errors" +) + +// DefaultEndorsementFactory returns an endorsement plugin factory which returns plugins +// that behave as the default endorsement system chaincode +type DefaultEndorsementFactory struct { +} + +// New returns an endorsement plugin that behaves as the default endorsement system chaincode +func (*DefaultEndorsementFactory) New() Plugin { + return &DefaultEndorsement{} +} + +// DefaultEndorsement is an endorsement plugin that behaves as the default endorsement system chaincode +type DefaultEndorsement struct { + SigningIdentityFetcher +} + +// Endorse signs the given payload(ProposalResponsePayload bytes), and optionally mutates it. +// Returns: +// The Endorsement: A signature over the payload, and an identity that is used to verify the signature +// The payload that was given as input (could be modified within this function) +// Or error on failure +func (e *DefaultEndorsement) Endorse(prpBytes []byte, sp *peer.SignedProposal) (*peer.Endorsement, []byte, error) { + signer, err := e.SigningIdentityForRequest(sp) + if err != nil { + return nil, nil, errors.Wrap(err, "failed fetching signing identity") + } + // serialize the signing identity + identityBytes, err := signer.Serialize() + if err != nil { + return nil, nil, errors.Wrapf(err, "could not serialize the signing identity") + } + + // sign the concatenation of the proposal response and the serialized endorser identity with this endorser's key + signature, err := signer.Sign(append(prpBytes, identityBytes...)) + if err != nil { + return nil, nil, errors.Wrapf(err, "could not sign the proposal response payload") + } + endorsement := &peer.Endorsement{Signature: signature, Endorser: identityBytes} + return endorsement, prpBytes, nil +} + +// Init injects dependencies into the instance of the Plugin +func (e *DefaultEndorsement) Init(dependencies ...Dependency) error { + for _, dep := range dependencies { + sIDFetcher, isSigningIdentityFetcher := dep.(SigningIdentityFetcher) + if !isSigningIdentityFetcher { + continue + } + e.SigningIdentityFetcher = sIDFetcher + return nil + } + return errors.New("could not find SigningIdentityFetcher in dependencies") +} diff --git a/core/handlers/endorsement/builtin/default_endorsement_test.go b/core/handlers/endorsement/builtin/default_endorsement_test.go new file mode 100644 index 00000000000..18f6d02465b --- /dev/null +++ b/core/handlers/endorsement/builtin/default_endorsement_test.go @@ -0,0 +1,63 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package builtin_test + +import ( + "testing" + + "github.com/hyperledger/fabric/core/endorser/mocks" + "github.com/hyperledger/fabric/core/handlers/endorsement/builtin" + mocks2 "github.com/hyperledger/fabric/core/handlers/endorsement/builtin/mocks" + "github.com/hyperledger/fabric/protos/peer" + "github.com/pkg/errors" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +func TestDefaultEndorsement(t *testing.T) { + factory := &builtin.DefaultEndorsementFactory{} + endorser := factory.New() + + // Scenario I: Don't pass any dependencies, and observe that the initialization fails + err := endorser.Init() + assert.Equal(t, "could not find SigningIdentityFetcher in dependencies", err.Error()) + + // Scenario II: Pass into init a SigningIdentityFetcher + sif := &mocks.SigningIdentityFetcher{} + // Pass also another item just to ensure it is ignored + err = endorser.Init("foo", sif) + assert.NoError(t, err) + + // Scenario III: Obtaining a signing identity fails + sif.On("SigningIdentityForRequest", mock.Anything).Return(nil, errors.New("foo")).Once() + _, _, err = endorser.Endorse(nil, nil) + assert.Contains(t, err.Error(), "foo") + + // Scenario IV: Obtaining a signing identity succeeds but serializing the identity fails + sid := &mocks2.SigningIdentity{} + sid.On("Serialize").Return(nil, errors.New("bar")).Once() + sif.On("SigningIdentityForRequest", mock.Anything).Return(sid, nil) + _, _, err = endorser.Endorse(nil, nil) + assert.Contains(t, err.Error(), "bar") + + // Scenario V: Serializing the identity succeeds but signing fails + sid.On("Serialize").Return([]byte{1, 2, 3}, nil) + sid.On("Sign", mock.Anything).Return(nil, errors.New("baz")).Once() + _, _, err = endorser.Endorse([]byte{1, 1, 1, 1, 1}, nil) + assert.Contains(t, err.Error(), "baz") + + // Scenario VI: Signing succeeds + sid.On("Serialize").Return([]byte{1, 2, 3}, nil) + sid.On("Sign", mock.Anything).Return([]byte{10, 20, 30}, nil).Once() + endorsement, resp, err := endorser.Endorse([]byte{1, 1, 1, 1, 1}, nil) + assert.NoError(t, err) + assert.Equal(t, resp, []byte{1, 1, 1, 1, 1}) + assert.Equal(t, &peer.Endorsement{ + Signature: []byte{10, 20, 30}, + Endorser: []byte{1, 2, 3}, + }, endorsement) +} diff --git a/core/handlers/endorsement/builtin/escc.go b/core/handlers/endorsement/builtin/escc.go deleted file mode 100644 index 12015c394ea..00000000000 --- a/core/handlers/endorsement/builtin/escc.go +++ /dev/null @@ -1,57 +0,0 @@ -/* -Copyright IBM Corp. All Rights Reserved. - -SPDX-License-Identifier: Apache-2.0 -*/ - -package builtin - -import ( - "github.com/hyperledger/fabric/core/handlers/endorsement/api" - endorsement2 "github.com/hyperledger/fabric/core/handlers/endorsement/api/identities" - "github.com/hyperledger/fabric/protos/peer" - "github.com/pkg/errors" -) - -type DefaultESCCFactory struct { -} - -func (*DefaultESCCFactory) New() endorsement.Plugin { - return &DefaultESCC{} -} - -type DefaultESCC struct { - endorsement2.SigningIdentityFetcher -} - -func (e *DefaultESCC) Endorse(prpBytes []byte, sp *peer.SignedProposal) (*peer.Endorsement, []byte, error) { - signer, err := e.SigningIdentityForRequest(sp) - if err != nil { - return nil, nil, errors.Wrap(err, "failed fetching signing identity") - } - // serialize the signing identity - identityBytes, err := signer.Serialize() - if err != nil { - return nil, nil, errors.Wrapf(err, "could not serialize the signing identity") - } - - // sign the concatenation of the proposal response and the serialized endorser identity with this endorser's key - signature, err := signer.Sign(append(prpBytes, identityBytes...)) - if err != nil { - return nil, nil, errors.Wrapf(err, "could not sign the proposal response payload") - } - endorsement := &peer.Endorsement{Signature: signature, Endorser: identityBytes} - return endorsement, prpBytes, nil -} - -func (e *DefaultESCC) Init(dependencies ...endorsement.Dependency) error { - for _, dep := range dependencies { - sIDFetcher, isSigningIdentityFetcher := dep.(endorsement2.SigningIdentityFetcher) - if !isSigningIdentityFetcher { - continue - } - e.SigningIdentityFetcher = sIDFetcher - return nil - } - return errors.New("could not find SigningIdentityFetcher in dependencies") -} diff --git a/core/handlers/endorsement/builtin/mocks/signing_identity.go b/core/handlers/endorsement/builtin/mocks/signing_identity.go new file mode 100644 index 00000000000..0d1eb908413 --- /dev/null +++ b/core/handlers/endorsement/builtin/mocks/signing_identity.go @@ -0,0 +1,61 @@ +/* +Copyright IBM Corp. 2018 All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +// Code generated by mockery v1.0.0 +package mocks + +import mock "github.com/stretchr/testify/mock" + +// SigningIdentity is an autogenerated mock type for the SigningIdentity type +type SigningIdentity struct { + mock.Mock +} + +// Serialize provides a mock function with given fields: +func (_m *SigningIdentity) Serialize() ([]byte, error) { + ret := _m.Called() + + var r0 []byte + if rf, ok := ret.Get(0).(func() []byte); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]byte) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Sign provides a mock function with given fields: _a0 +func (_m *SigningIdentity) Sign(_a0 []byte) ([]byte, error) { + ret := _m.Called(_a0) + + var r0 []byte + if rf, ok := ret.Get(0).(func([]byte) []byte); ok { + r0 = rf(_a0) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]byte) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func([]byte) error); ok { + r1 = rf(_a0) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} diff --git a/core/handlers/endorsement/plugin/escc_plugin.go b/core/handlers/endorsement/plugin/plugin.go similarity index 66% rename from core/handlers/endorsement/plugin/escc_plugin.go rename to core/handlers/endorsement/plugin/plugin.go index d077b87d3e8..9156b022131 100644 --- a/core/handlers/endorsement/plugin/escc_plugin.go +++ b/core/handlers/endorsement/plugin/plugin.go @@ -10,28 +10,28 @@ import ( "errors" "fmt" - endorsement2 "github.com/hyperledger/fabric/core/handlers/endorsement/api" - endorsement3 "github.com/hyperledger/fabric/core/handlers/endorsement/api/identities" + . "github.com/hyperledger/fabric/core/handlers/endorsement/api" + . "github.com/hyperledger/fabric/core/handlers/endorsement/api/identities" "github.com/hyperledger/fabric/protos/peer" ) // To build the plugin, // run: -// go build -buildmode=plugin -o escc.so escc_plugin.go +// go build -buildmode=plugin -o escc.so plugin.go -// DefaultESCCFactory returns an endorsement plugin factory which returns plugins +// DefaultEndorsementFactory returns an endorsement plugin factory which returns plugins // that behave as the default endorsement system chaincode -type DefaultESCCFactory struct { +type DefaultEndorsementFactory struct { } // New returns an endorsement plugin that behaves as the default endorsement system chaincode -func (*DefaultESCCFactory) New() endorsement2.Plugin { - return &DefaultESCC{} +func (*DefaultEndorsementFactory) New() Plugin { + return &DefaultEndorsement{} } -// DefaultESCC is an endorsement plugin that behaves as the default endorsement system chaincode -type DefaultESCC struct { - endorsement3.SigningIdentityFetcher +// DefaultEndorsement is an endorsement plugin that behaves as the default endorsement system chaincode +type DefaultEndorsement struct { + SigningIdentityFetcher } // Endorse signs the given payload(ProposalResponsePayload bytes), and optionally mutates it. @@ -39,7 +39,7 @@ type DefaultESCC struct { // The Endorsement: A signature over the payload, and an identity that is used to verify the signature // The payload that was given as input (could be modified within this function) // Or error on failure -func (e *DefaultESCC) Endorse(prpBytes []byte, sp *peer.SignedProposal) (*peer.Endorsement, []byte, error) { +func (e *DefaultEndorsement) Endorse(prpBytes []byte, sp *peer.SignedProposal) (*peer.Endorsement, []byte, error) { signer, err := e.SigningIdentityForRequest(sp) if err != nil { return nil, nil, errors.New(fmt.Sprintf("failed fetching signing identity: %v", err)) @@ -60,9 +60,9 @@ func (e *DefaultESCC) Endorse(prpBytes []byte, sp *peer.SignedProposal) (*peer.E } // Init injects dependencies into the instance of the Plugin -func (e *DefaultESCC) Init(dependencies ...endorsement2.Dependency) error { +func (e *DefaultEndorsement) Init(dependencies ...Dependency) error { for _, dep := range dependencies { - sIDFetcher, isSigningIdentityFetcher := dep.(endorsement3.SigningIdentityFetcher) + sIDFetcher, isSigningIdentityFetcher := dep.(SigningIdentityFetcher) if !isSigningIdentityFetcher { continue } @@ -73,6 +73,6 @@ func (e *DefaultESCC) Init(dependencies ...endorsement2.Dependency) error { } // NewPluginFactory is the function ran by the plugin infrastructure to create an endorsement plugin factory. -func NewPluginFactory() endorsement2.PluginFactory { - return &DefaultESCCFactory{} +func NewPluginFactory() PluginFactory { + return &DefaultEndorsementFactory{} } diff --git a/core/handlers/library/library.go b/core/handlers/library/library.go index 028194ab105..9506d9746e8 100644 --- a/core/handlers/library/library.go +++ b/core/handlers/library/library.go @@ -42,6 +42,6 @@ func (r *HandlerLibrary) DefaultDecorator() decoration.Decorator { return decorator.NewDecorator() } -func (r *HandlerLibrary) ESCC() endorsement.PluginFactory { - return &builtin.DefaultESCCFactory{} +func (r *HandlerLibrary) DefaultEndorsement() endorsement.PluginFactory { + return &builtin.DefaultEndorsementFactory{} } diff --git a/core/mocks/endorser/support.go b/core/mocks/endorser/support.go index d88480676b5..cece9f035d7 100644 --- a/core/mocks/endorser/support.go +++ b/core/mocks/endorser/support.go @@ -9,14 +9,18 @@ package endorser import ( "github.com/hyperledger/fabric/common/channelconfig" "github.com/hyperledger/fabric/core/common/ccprovider" + "github.com/hyperledger/fabric/core/endorser" "github.com/hyperledger/fabric/core/ledger" mc "github.com/hyperledger/fabric/core/mocks/ccprovider" "github.com/hyperledger/fabric/protos/common" pb "github.com/hyperledger/fabric/protos/peer" + "github.com/stretchr/testify/mock" "golang.org/x/net/context" ) type MockSupport struct { + *mock.Mock + *endorser.PluginEndorser IsSysCCAndNotInvokableExternalRv bool IsSysCCRv bool ExecuteCDSResp *pb.Response @@ -39,6 +43,24 @@ type MockSupport struct { GetApplicationConfigBoolRv bool } +func (s *MockSupport) Serialize() ([]byte, error) { + args := s.Called() + return args.Get(0).([]byte), args.Error(1) +} + +func (s *MockSupport) NewQueryCreator(channel string) (endorser.QueryCreator, error) { + panic("implement me") +} + +func (s *MockSupport) Sign(message []byte) ([]byte, error) { + args := s.Called(message) + return args.Get(0).([]byte), args.Error(1) +} + +func (s *MockSupport) ChannelState(channel string) (endorser.QueryCreator, error) { + panic("implement me") +} + func (s *MockSupport) IsSysCCAndNotInvokableExternal(name string) bool { return s.IsSysCCAndNotInvokableExternalRv } diff --git a/core/scc/escc/endorser_onevalidsignature.go b/core/scc/escc/endorser_onevalidsignature.go deleted file mode 100644 index f138b9fe7f6..00000000000 --- a/core/scc/escc/endorser_onevalidsignature.go +++ /dev/null @@ -1,180 +0,0 @@ -/* -Copyright IBM Corp. All Rights Reserved. - -SPDX-License-Identifier: Apache-2.0 -*/ - -package escc - -import ( - "fmt" - - "github.com/hyperledger/fabric/common/flogging" - "github.com/hyperledger/fabric/core/chaincode/shim" - "github.com/hyperledger/fabric/core/common/sysccprovider" - pb "github.com/hyperledger/fabric/protos/peer" - "github.com/hyperledger/fabric/protos/utils" - putils "github.com/hyperledger/fabric/protos/utils" - - mspmgmt "github.com/hyperledger/fabric/msp/mgmt" -) - -var logger = flogging.MustGetLogger("escc") - -// New returns a new instance of the ESCC -// Typically, only one will be constructed per peer instance -func New() *EndorserOneValidSignature { - return &EndorserOneValidSignature{} -} - -// NewAsChaincode wraps New() to return it as a chaincode -func NewAsChaincode(sccp sysccprovider.SystemChaincodeProvider) shim.Chaincode { - return New() -} - -// EndorserOneValidSignature implements the default endorsement policy, which is to -// sign the proposal hash and the read-write set -type EndorserOneValidSignature struct { -} - -// Init is called once when the chaincode started the first time -func (e *EndorserOneValidSignature) Init(stub shim.ChaincodeStubInterface) pb.Response { - logger.Infof("Successfully initialized ESCC") - return shim.Success(nil) -} - -// Invoke is called to endorse the specified Proposal -// For now, we sign the input and return the endorsed result. Later we can expand -// the chaincode to provide more sophisticate policy processing such as enabling -// policy specification to be coded as a transaction of the chaincode and Client -// could select which policy to use for endorsement using parameter -// @return a marshalled proposal response -// Note that Peer calls this function with 4 mandatory arguments (and 2 optional ones): -// args[0] - function name (not used now) -// args[1] - serialized Header object -// args[2] - serialized ChaincodeProposalPayload object -// args[3] - ChaincodeID of executing chaincode -// args[4] - result of executing chaincode -// args[5] - binary blob of simulation results -// args[6] - serialized events -// args[7] - payloadVisibility -// -// NOTE: this chaincode is meant to sign another chaincode's simulation -// results. It should not manipulate state as any state change will be -// silently discarded: the only state changes that will be persisted if -// this endorsement is successful is what we are about to sign, which by -// definition can't be a state change of our own. -func (e *EndorserOneValidSignature) Invoke(stub shim.ChaincodeStubInterface) pb.Response { - args := stub.GetArgs() - if len(args) < 6 { - return shim.Error(fmt.Sprintf("Incorrect number of arguments (expected a minimum of 5, provided %d)", len(args))) - } else if len(args) > 8 { - return shim.Error(fmt.Sprintf("Incorrect number of arguments (expected a maximum of 7, provided %d)", len(args))) - } - - logger.Debugf("ESCC starts: %d args", len(args)) - - // handle the header - var hdr []byte - if args[1] == nil { - return shim.Error("serialized Header object is null") - } - - hdr = args[1] - - // handle the proposal payload - var payl []byte - if args[2] == nil { - return shim.Error("serialized ChaincodeProposalPayload object is null") - } - - payl = args[2] - - // handle ChaincodeID - if args[3] == nil { - return shim.Error("ChaincodeID is null") - } - - ccid, err := putils.UnmarshalChaincodeID(args[3]) - if err != nil { - return shim.Error(err.Error()) - } - - // handle executing chaincode result - // Status code < shim.ERRORTHRESHOLD can be endorsed - if args[4] == nil { - return shim.Error("Response of chaincode executing is null") - } - - response, err := putils.GetResponse(args[4]) - if err != nil { - return shim.Error(fmt.Sprintf("Failed to get Response of executing chaincode: %s", err.Error())) - } - - if response.Status >= shim.ERRORTHRESHOLD { - return *response - } - - // handle simulation results - var results []byte - if args[5] == nil { - return shim.Error("simulation results are null") - } - - results = args[5] - - // Handle serialized events if they have been provided - // they might be nil in case there's no events but there - // is a visibility field specified as the next arg - events := []byte("") - if len(args) > 6 && args[6] != nil { - events = args[6] - } - - // Handle payload visibility (it's an optional argument) - // currently the fabric only supports full visibility: this means that - // there are no restrictions on which parts of the proposal payload will - // be visible in the final transaction; this default approach requires - // no additional instructions in the PayloadVisibility field; however - // the fabric may be extended to encode more elaborate visibility - // mechanisms that shall be encoded in this field (and handled - // appropriately by the peer) - var visibility []byte - if len(args) > 7 { - visibility = args[7] - } - - // obtain the default signing identity for this peer; it will be used to sign this proposal response - localMsp := mspmgmt.GetLocalMSP() - if localMsp == nil { - return shim.Error("Nil local MSP manager") - } - - signingEndorser, err := localMsp.GetDefaultSigningIdentity() - if err != nil { - return shim.Error(fmt.Sprintf("Could not obtain the default signing identity, err %s", err)) - } - - // obtain a proposal response - presp, err := utils.CreateProposalResponse(hdr, payl, response, results, events, ccid, visibility, signingEndorser) - if err != nil { - return shim.Error(err.Error()) - } - - // marshall the proposal response so that we return its bytes - prBytes, err := utils.GetBytesProposalResponse(presp) - if err != nil { - return shim.Error(fmt.Sprintf("Could not marshal ProposalResponse: err %s", err)) - } - - pResp, err := utils.GetProposalResponse(prBytes) - if err != nil { - return shim.Error(err.Error()) - } - if pResp.Response == nil { - fmt.Println("GetProposalResponse get empty Response") - } - - logger.Debugf("ESCC exits successfully") - return shim.Success(prBytes) -} diff --git a/core/scc/escc/endorser_onevalidsignature_test.go b/core/scc/escc/endorser_onevalidsignature_test.go deleted file mode 100644 index da6ebcdaf17..00000000000 --- a/core/scc/escc/endorser_onevalidsignature_test.go +++ /dev/null @@ -1,386 +0,0 @@ -/* -Copyright IBM Corp. 2016 All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ -package escc - -import ( - "bytes" - "errors" - "fmt" - "os" - "testing" - - "github.com/golang/protobuf/proto" - "github.com/hyperledger/fabric/common/mocks/config" - "github.com/hyperledger/fabric/common/util" - "github.com/hyperledger/fabric/core/chaincode/shim" - "github.com/hyperledger/fabric/core/common/validation" - mspmgmt "github.com/hyperledger/fabric/msp/mgmt" - "github.com/hyperledger/fabric/msp/mgmt/testtools" - "github.com/hyperledger/fabric/protos/common" - pb "github.com/hyperledger/fabric/protos/peer" - putils "github.com/hyperledger/fabric/protos/utils" - "github.com/stretchr/testify/assert" -) - -func TestInit(t *testing.T) { - e := new(EndorserOneValidSignature) - stub := shim.NewMockStub("endorseronevalidsignature", e) - - args := [][]byte{[]byte("SampleOrg"), []byte("PEER")} - if res := stub.MockInit("1", args); res.Status != shim.OK { - fmt.Println("Init failed", string(res.Message)) - t.FailNow() - } -} - -func TestInvoke(t *testing.T) { - e := new(EndorserOneValidSignature) - stub := shim.NewMockStub("endorseronevalidsignature", e) - successResponse := &pb.Response{Status: 200, Payload: []byte("payload")} - failResponse := &pb.Response{Status: 500, Message: "error"} - ccFailResponse := &pb.Response{Status: 400, Message: "chaincode error"} - successRes, _ := putils.GetBytesResponse(successResponse) - failRes, _ := putils.GetBytesResponse(failResponse) - ccFailRes, _ := putils.GetBytesResponse(ccFailResponse) - ccid := &pb.ChaincodeID{Name: "foo", Version: "v1"} - ccidBytes, err := proto.Marshal(ccid) - if err != nil { - t.Fail() - t.Fatalf("couldn't marshal ChaincodeID: err %s", err) - return - } - - // Initialize ESCC supplying the identity of the signer - args := [][]byte{[]byte("SampleOrg"), []byte("PEER")} - if res := stub.MockInit("1", args); res.Status != shim.OK { - fmt.Println("Init failed", string(res.Message)) - t.FailNow() - } - - // Failed path: Not enough parameters - args = [][]byte{[]byte("test")} - if res := stub.MockInvoke("1", args); res.Status == shim.OK { - t.Fatalf("escc invoke should have failed with invalid number of args: %v", args) - } - - // Failed path: Not enough parameters - args = [][]byte{[]byte("test"), []byte("test")} - if res := stub.MockInvoke("1", args); res.Status == shim.OK { - t.Fatalf("escc invoke should have failed with invalid number of args: %v", args) - } - - // Failed path: Not enough parameters - args = [][]byte{[]byte("test"), []byte("test"), []byte("test")} - if res := stub.MockInvoke("1", args); res.Status == shim.OK { - t.Fatalf("escc invoke should have failed with invalid number of args: %v", args) - } - - // Failed path: Not enough parameters - args = [][]byte{[]byte("test"), []byte("test"), []byte("test"), []byte("test")} - if res := stub.MockInvoke("1", args); res.Status == shim.OK { - t.Fatalf("escc invoke should have failed with invalid number of args: %v", args) - } - - // Failed path: Not enough parameters - args = [][]byte{[]byte("test"), []byte("test"), []byte("test"), []byte("test"), []byte("test")} - if res := stub.MockInvoke("1", args); res.Status == shim.OK { - t.Fatalf("escc invoke should have failed with invalid number of args: %v", args) - } - - // Too many parameters - a := []byte("test") - args = [][]byte{a, a, a, a, a, a, a, a, a} - if res := stub.MockInvoke("1", args); res.Status == shim.OK { - t.Fatalf("escc invoke should have failed with invalid number of args: %v", args) - } - - // Failed path: ccid is null - args = [][]byte{[]byte("test"), []byte("test"), []byte("test"), nil, successRes, []byte("test")} - if res := stub.MockInvoke("1", args); res.Status == shim.OK { - fmt.Println("Invoke", args, "failed", string(res.Message)) - t.Fatalf("escc invoke should have failed with a null header. args: %v", args) - } - - // Failed path: ccid is bogus - args = [][]byte{[]byte("test"), []byte("test"), []byte("test"), []byte("barf"), successRes, []byte("test")} - if res := stub.MockInvoke("1", args); res.Status == shim.OK { - fmt.Println("Invoke", args, "failed", string(res.Message)) - t.Fatalf("escc invoke should have failed with a null header. args: %v", args) - } - - // Failed path: response is bogus - args = [][]byte{[]byte("test"), []byte("test"), []byte("test"), ccidBytes, []byte("barf"), []byte("test")} - if res := stub.MockInvoke("1", args); res.Status == shim.OK { - fmt.Println("Invoke", args, "failed", string(res.Message)) - t.Fatalf("escc invoke should have failed with a null header. args: %v", args) - } - - // Failed path: header is null - args = [][]byte{[]byte("test"), nil, []byte("test"), ccidBytes, successRes, []byte("test")} - if res := stub.MockInvoke("1", args); res.Status == shim.OK { - fmt.Println("Invoke", args, "failed", string(res.Message)) - t.Fatalf("escc invoke should have failed with a null header. args: %v", args) - } - - // Failed path: payload is null - args = [][]byte{[]byte("test"), []byte("test"), nil, ccidBytes, successRes, []byte("test")} - if res := stub.MockInvoke("1", args); res.Status == shim.OK { - fmt.Println("Invoke", args, "failed", string(res.Message)) - t.Fatalf("escc invoke should have failed with a null payload. args: %v", args) - } - - // Failed path: response is null - args = [][]byte{[]byte("test"), []byte("test"), []byte("test"), ccidBytes, nil, []byte("test")} - if res := stub.MockInvoke("1", args); res.Status == shim.OK { - fmt.Println("Invoke", args, "failed", string(res.Message)) - t.Fatalf("escc invoke should have failed with a null response. args: %v", args) - } - - // Failed path: action struct is null - args = [][]byte{[]byte("test"), []byte("test"), []byte("test"), ccidBytes, successRes, nil} - if res := stub.MockInvoke("1", args); res.Status == shim.OK { - fmt.Println("Invoke", args, "failed", string(res.Message)) - t.Fatalf("escc invoke should have failed with a null action struct. args: %v", args) - } - - // Failed path: status code = 500 - args = [][]byte{[]byte("test"), []byte("test"), []byte("test"), ccidBytes, failRes, []byte("test")} - res := stub.MockInvoke("1", args) - assert.NotEqual(t, res.Status, shim.OK, "Invoke should have failed with status code: %d", failResponse.Status) - assert.Equal(t, res, *failResponse) - - // Failed path: status code = 400 - args = [][]byte{[]byte("test"), []byte("test"), []byte("test"), ccidBytes, ccFailRes, []byte("test")} - res = stub.MockInvoke("1", args) - assert.NotEqual(t, res.Status, shim.OK, "Invoke should have failed with status code: %d", ccFailResponse.Status) - assert.Equal(t, res, *ccFailResponse) - - // Successful path - create a proposal - cs := &pb.ChaincodeSpec{ - ChaincodeId: ccid, - Type: pb.ChaincodeSpec_GOLANG, - Input: &pb.ChaincodeInput{Args: [][]byte{[]byte("some"), []byte("args")}}} - - cis := &pb.ChaincodeInvocationSpec{ChaincodeSpec: cs} - - sId, err := mspmgmt.GetLocalMSP().GetDefaultSigningIdentity() - if err != nil { - t.Fail() - t.Fatalf("couldn't obtain identity: err %s", err) - return - } - - sIdBytes, err := sId.Serialize() - if err != nil { - t.Fail() - t.Fatalf("couldn't serialize identity: err %s", err) - return - } - - proposal, _, err := putils.CreateChaincodeProposal(common.HeaderType_ENDORSER_TRANSACTION, util.GetTestChainID(), cis, sIdBytes) - if err != nil { - t.Fail() - t.Fatalf("couldn't generate chaincode proposal: err %s", err) - return - } - - simRes := []byte("simulation_result") - - // bogus header - args = [][]byte{[]byte(""), []byte("barf"), proposal.Payload, ccidBytes, successRes, simRes} - if res := stub.MockInvoke("1", args); res.Status == shim.OK { - fmt.Println("Invoke", args, "failed", string(res.Message)) - t.Fatalf("escc invoke should have failed with a null response. args: %v", args) - } - - // success test 1: invocation with mandatory args only - args = [][]byte{[]byte(""), proposal.Header, proposal.Payload, ccidBytes, successRes, simRes} - res = stub.MockInvoke("1", args) - if res.Status != shim.OK { - t.Fail() - t.Fatalf("escc invoke failed with: %s", res.Message) - return - } - - err = validateProposalResponse(res.Payload, proposal, cs.ChaincodeId, nil, successResponse, simRes, nil) - if err != nil { - t.Fail() - t.Fatalf("%s", err) - return - } - - // success test 2: invocation with mandatory args + events - events := []byte("events") - - args = [][]byte{[]byte(""), proposal.Header, proposal.Payload, ccidBytes, successRes, simRes, events} - res = stub.MockInvoke("1", args) - if res.Status != shim.OK { - t.Fail() - t.Fatalf("escc invoke failed with: %s", res.Message) - return - } - - err = validateProposalResponse(res.Payload, proposal, cs.ChaincodeId, nil, successResponse, simRes, events) - if err != nil { - t.Fail() - t.Fatalf("%s", err) - return - } - - // success test 3: invocation with mandatory args + events and visibility - args = [][]byte{[]byte(""), proposal.Header, proposal.Payload, ccidBytes, successRes, simRes, events, nil} - res = stub.MockInvoke("1", args) - if res.Status != shim.OK { - t.Fail() - t.Fatalf("escc invoke failed with: %s", res.Message) - return - } - - err = validateProposalResponse(res.Payload, proposal, cs.ChaincodeId, []byte{}, successResponse, simRes, events) - if err != nil { - t.Fail() - t.Fatalf("%s", err) - return - } -} - -func validateProposalResponse(prBytes []byte, proposal *pb.Proposal, ccid *pb.ChaincodeID, visibility []byte, response *pb.Response, simRes []byte, events []byte) error { - if visibility == nil { - // TODO: set visibility to the default visibility mode once modes are defined - } - - pResp, err := putils.GetProposalResponse(prBytes) - if err != nil { - return err - } - - // check the version - if pResp.Version != 1 { - return fmt.Errorf("invalid version: %d", pResp.Version) - } - - // check the response status - if pResp.Response.Status != 200 { - return fmt.Errorf("invalid response status: %d", pResp.Response.Status) - } - - // extract ProposalResponsePayload - prp, err := putils.GetProposalResponsePayload(pResp.Payload) - if err != nil { - return fmt.Errorf("could not unmarshal the proposal response structure: err %s", err) - } - - // TODO: validate the epoch - - hdr, err := putils.GetHeader(proposal.Header) - if err != nil { - return fmt.Errorf("could not unmarshal the proposal header structure: err %s", err) - } - - // recompute proposal hash - pHash, err := putils.GetProposalHash1(hdr, proposal.Payload, visibility) - if err != nil { - return fmt.Errorf("could not obtain proposalHash: err %s", err) - } - - // validate that proposal hash matches - if bytes.Compare(pHash, prp.ProposalHash) != 0 { - return errors.New("proposal hash does not match") - } - - // extract the chaincode action - cact, err := putils.GetChaincodeAction(prp.Extension) - if err != nil { - return fmt.Errorf("could not unmarshal the chaincode action structure: err %s", err) - } - - // validate that the response match - if cact.Response.Status != response.Status { - return errors.New("response status do not match") - } - if cact.Response.Message != response.Message { - return errors.New("response message do not match") - } - if bytes.Compare(cact.Response.Payload, response.Payload) != 0 { - return errors.New("response payload do not match") - } - - // validate that the results match - if bytes.Compare(cact.Results, simRes) != 0 { - return errors.New("results do not match") - } - - // validate that the events match - if bytes.Compare(cact.Events, events) != 0 { - return errors.New("events do not match") - } - - // validate that the ChaincodeID match - if cact.ChaincodeId.Name != ccid.Name { - return errors.New("ChaincodeID name do not match") - } - if cact.ChaincodeId.Version != ccid.Version { - return errors.New("ChaincodeID version do not match") - } - if cact.ChaincodeId.Path != ccid.Path { - return errors.New("ChaincodeID path do not match") - } - - // get the identity of the endorser - endorser, err := mspmgmt.GetManagerForChain(util.GetTestChainID()).DeserializeIdentity(pResp.Endorsement.Endorser) - if err != nil { - return fmt.Errorf("Failed to deserialize endorser identity, err %s", err) - } - - // ensure that endorser has a valid certificate - err = endorser.Validate() - if err != nil { - return fmt.Errorf("The endorser certificate is not valid, err %s", err) - } - - err = endorser.Verify(append(pResp.Payload, pResp.Endorsement.Endorser...), pResp.Endorsement.Signature) - if err != nil { - return fmt.Errorf("The endorser's signature over the proposal response is not valid, err %s", err) - } - - // as extra, we assemble a transaction, sign it and then validate it - - // obtain signer for the transaction - sId, err := mspmgmt.GetLocalMSP().GetDefaultSigningIdentity() - if err != nil { - return fmt.Errorf("couldn't obtain identity: err %s", err) - } - - // generate a transaction - tx, err := putils.CreateSignedTx(proposal, sId, pResp) - if err != nil { - return err - } - - // validate the transaction - _, txResult := validation.ValidateTransaction(tx, &config.MockApplicationCapabilities{}) - if txResult != pb.TxValidationCode_VALID { - return err - } - - return nil -} - -func TestMain(m *testing.M) { - msptesttools.LoadMSPSetupForTesting() - - os.Exit(m.Run()) -} diff --git a/core/scc/importsysccs.go b/core/scc/importsysccs.go index c450a5be7ec..c5b4df6b884 100644 --- a/core/scc/importsysccs.go +++ b/core/scc/importsysccs.go @@ -9,7 +9,6 @@ package scc import ( //import system chaincodes here "github.com/hyperledger/fabric/core/scc/cscc" - "github.com/hyperledger/fabric/core/scc/escc" "github.com/hyperledger/fabric/core/scc/lscc" "github.com/hyperledger/fabric/core/scc/qscc" "github.com/hyperledger/fabric/core/scc/vscc" @@ -34,13 +33,6 @@ var systemChaincodes = []*SystemChaincode{ InvokableExternal: true, // lscc is invoked to deploy new chaincodes InvokableCC2CC: true, // lscc can be invoked by other chaincodes }, - { - Enabled: true, - Name: "escc", - Path: "github.com/hyperledger/fabric/core/scc/escc", - InitArgs: nil, - Chaincode: escc.NewAsChaincode, - }, { Enabled: true, Name: "vscc", diff --git a/core/scc/lscc/lscc.go b/core/scc/lscc/lscc.go index 1985c0903c5..e78b62cda06 100644 --- a/core/scc/lscc/lscc.go +++ b/core/scc/lscc/lscc.go @@ -133,10 +133,6 @@ func NewAsChaincode(sccp sysccprovider.SystemChaincodeProvider) shim.Chaincode { //create the chaincode on the given chain func (lscc *lifeCycleSysCC) putChaincodeData(stub shim.ChaincodeStubInterface, cd *ccprovider.ChaincodeData) error { - // check that escc and vscc are real system chaincodes - if !lscc.sccprovider.IsSysCC(string(cd.Escc)) { - return fmt.Errorf("%s is not a valid endorsement system chaincode", string(cd.Escc)) - } if !lscc.sccprovider.IsSysCC(string(cd.Vscc)) { return fmt.Errorf("%s is not a valid validation system chaincode", string(cd.Vscc)) } diff --git a/core/scc/lscc/lscc_test.go b/core/scc/lscc/lscc_test.go index 536649a0f85..dc559adcb94 100644 --- a/core/scc/lscc/lscc_test.go +++ b/core/scc/lscc/lscc_test.go @@ -211,15 +211,6 @@ func TestDeploy(t *testing.T) { testDeploy(t, "example02", "1.0", path, false, false, true, "barf", scc, stub, nil) - scc = New(NewMockProvider()) - scc.support = &lscc.MockSupport{} - stub = shim.NewMockStub("lscc", scc) - res = stub.MockInit("1", nil) - assert.Equal(t, res.Status, int32(shim.OK), res.Message) - scc.sccprovider.(*mscc.MocksccProviderImpl).SysCCMap = map[string]bool{"escc": false} - - testDeploy(t, "example02", "1.0", path, false, false, true, "escc is not a valid endorsement system chaincode", scc, stub, nil) - scc = New(NewMockProvider()) scc.support = &lscc.MockSupport{} stub = shim.NewMockStub("lscc", scc) diff --git a/peer/node/start.go b/peer/node/start.go index b86ce8a3ee1..92894179dc9 100644 --- a/peer/node/start.go +++ b/peer/node/start.go @@ -34,6 +34,8 @@ import ( "github.com/hyperledger/fabric/core/common/sysccprovider" "github.com/hyperledger/fabric/core/endorser" authHandler "github.com/hyperledger/fabric/core/handlers/auth" + endorsement2 "github.com/hyperledger/fabric/core/handlers/endorsement/api" + endorsement3 "github.com/hyperledger/fabric/core/handlers/endorsement/api/identities" "github.com/hyperledger/fabric/core/handlers/library" "github.com/hyperledger/fabric/core/ledger/cceventmgmt" "github.com/hyperledger/fabric/core/ledger/ledgermgmt" @@ -239,19 +241,32 @@ func serve(args []string) error { return service.GetGossipService().DistributePrivateData(channel, txID, privateData, blkHt) } - serverEndorser := endorser.NewEndorserServer( - privDataDist, - &endorser.SupportImpl{ - Peer: peer.Default, - PeerSupport: peer.DefaultSupport, - ChaincodeSupport: chaincodeSupport, - }, - ) + signingIdentity := mgmt.GetLocalSigningIdentityOrPanic() + serializedIdentity, err := signingIdentity.Serialize() + if err != nil { + logger.Panicf("Failed serializing self identity: %v", err) + } + libConf := library.Config{} if err = viperutil.EnhancedExactUnmarshalKey("peer.handlers", &libConf); err != nil { return errors.WithMessage(err, "could not load YAML config") } - authFilters := library.InitRegistry(libConf).Lookup(library.Auth).([]authHandler.Filter) + reg := library.InitRegistry(libConf) + + authFilters := reg.Lookup(library.Auth).([]authHandler.Filter) + endorserSupport := &endorser.SupportImpl{ + SignerSupport: signingIdentity, + Peer: peer.Default, + PeerSupport: peer.DefaultSupport, + ChaincodeSupport: chaincodeSupport, + } + pluginsByName := reg.Lookup(library.Endorsement).(map[string]endorsement2.PluginFactory) + signingIdentityFetcher := (endorsement3.SigningIdentityFetcher)(endorserSupport) + channelStateRetriever := endorser.ChannelStateRetriever(endorserSupport) + pluginMapper := endorser.MapBasedPluginMapper(pluginsByName) + pluginEndorser := endorser.NewPluginEndorser(channelStateRetriever, signingIdentityFetcher, pluginMapper) + endorserSupport.PluginEndorser = pluginEndorser + serverEndorser := endorser.NewEndorserServer(privDataDist, endorserSupport) auth := authHandler.ChainFilters(serverEndorser, authFilters...) // Register the Endorser server pb.RegisterEndorserServer(peerServer.Server(), auth) @@ -259,11 +274,6 @@ func serve(args []string) error { // Initialize gossip component bootstrap := viper.GetStringSlice("peer.gossip.bootstrap") - serializedIdentity, err := mgmt.GetLocalSigningIdentityOrPanic().Serialize() - if err != nil { - logger.Panicf("Failed serializing self identity: %v", err) - } - messageCryptoService := peergossip.NewMCS( peer.NewChannelPolicyManagerGetter(), localmsp.NewSigner(), diff --git a/sampleconfig/core.yaml b/sampleconfig/core.yaml index bd4fc0b6dfb..31ab71ef15f 100644 --- a/sampleconfig/core.yaml +++ b/sampleconfig/core.yaml @@ -361,6 +361,7 @@ peer: # objects passing within the peer, such as: # Auth filter - reject or forward proposals from clients # Decorators - append or mutate the chaincode input passed to the chaincode + # Endorsers - Custom signing over proposal response payload and its mutation # Valid handler definition contains: # - A name which is a factory method name defined in # core/handlers/library/library.go for statically compiled handlers @@ -379,6 +380,15 @@ peer: # - # name: DecoratorTwo # library: /opt/lib/decorator.so + # Endorsers are configured as a map that its keys are the endorsement system chaincodes that are being overridden. + # Below is an example that overrides the default ESCC and uses an endorsement plugin that has the same functionality + # as the default ESCC. + # If the 'library' property is missing, the name is used as the constructor method in the builtin library similar + # to auth filters and decorators. + # endorsers: + # escc: + # name: DefaultESCC + # library: /etc/hyperledger/fabric/plugin/escc.so handlers: authFilters: - @@ -388,7 +398,10 @@ peer: decorators: - name: DefaultDecorator - + endorsers: + escc: + name: DefaultEndorsement + # library: /etc/hyperledger/fabric/plugin/escc.so # Number of goroutines that will execute transaction validation in parallel. # By default, the peer chooses the number of CPUs on the machine. Set this # variable to override that choice.