diff --git a/bddtests/chaincode.go b/bddtests/chaincode.go index acb0062b17b..6837205b310 100644 --- a/bddtests/chaincode.go +++ b/bddtests/chaincode.go @@ -39,7 +39,7 @@ func createPropsalID() string { } // createChaincodeDeploymentSpec Returns a deployment proposal of chaincode type -func createProposalForChaincode(ccChaincodeDeploymentSpec *pb.ChaincodeDeploymentSpec) (proposal *pb.Proposal, err error) { +func createProposalForChaincode(ccChaincodeDeploymentSpec *pb.ChaincodeDeploymentSpec, creator []byte) (proposal *pb.Proposal, err error) { var ccDeploymentSpecBytes []byte if ccDeploymentSpecBytes, err = proto.Marshal(ccChaincodeDeploymentSpec); err != nil { return nil, fmt.Errorf("Error creating proposal from ChaincodeDeploymentSpec: %s", err) @@ -49,5 +49,5 @@ func createProposalForChaincode(ccChaincodeDeploymentSpec *pb.ChaincodeDeploymen CtorMsg: &pb.ChaincodeInput{Args: [][]byte{[]byte("deploy"), []byte("default"), ccDeploymentSpecBytes}}} lcChaincodeInvocationSpec := &pb.ChaincodeInvocationSpec{ChaincodeSpec: lcChaincodeSpec} // make proposal - return putils.CreateChaincodeProposal(lcChaincodeInvocationSpec) + return putils.CreateChaincodeProposal(lcChaincodeInvocationSpec, creator) } diff --git a/bddtests/endorser_test.go b/bddtests/endorser_test.go index e3cac3716a6..6ce25ef84b9 100644 --- a/bddtests/endorser_test.go +++ b/bddtests/endorser_test.go @@ -94,7 +94,8 @@ func (b *BDDContext) userCreatesADeploymentProposalUsingChaincodeDeploymentSpec( return errRetFunc() } var proposal *pb.Proposal - if proposal, err = createProposalForChaincode(ccDeploymentSpec); err != nil { + // TODO: how should we get a cert from the command line? + if proposal, err = createProposalForChaincode(ccDeploymentSpec, []byte("cert")); err != nil { } if _, err = userRegistration.SetTagValue(proposalAlias, proposal); err != nil { diff --git a/core/chaincode/chaincode_support.go b/core/chaincode/chaincode_support.go index dc5edecd6ea..7f86f2ae618 100644 --- a/core/chaincode/chaincode_support.go +++ b/core/chaincode/chaincode_support.go @@ -521,7 +521,7 @@ func (chaincodeSupport *ChaincodeSupport) Launch(context context.Context, t *pb. var depPayload []byte //hopefully we are restarting from existing image and the deployed transaction exists - depPayload, err = getCDSFromLCCC(context, string(DefaultChain), chaincode) + depPayload, err = GetCDSFromLCCC(context, string(DefaultChain), chaincode) if err != nil { return cID, cMsg, fmt.Errorf("Could not get deployment transaction from LCCC for %s - %s", chaincode, err) } diff --git a/core/chaincode/chaincodeexec.go b/core/chaincode/chaincodeexec.go index 553aac7aa5f..4930062d144 100644 --- a/core/chaincode/chaincodeexec.go +++ b/core/chaincode/chaincodeexec.go @@ -38,7 +38,7 @@ func createTx(typ pb.Transaction_Type, ccname string, args [][]byte) (*pb.Transa return tx, nil } -func getCDSFromLCCC(ctxt context.Context, chainID string, chaincodeID string) ([]byte, error) { +func GetCDSFromLCCC(ctxt context.Context, chainID string, chaincodeID string) ([]byte, error) { payload, _, err := ExecuteChaincode(ctxt, pb.Transaction_CHAINCODE_INVOKE, string(DefaultChain), "lccc", [][]byte{[]byte("getdepspec"), []byte(chainID), []byte(chaincodeID)}) return payload, err } diff --git a/core/endorser/config.go b/core/endorser/config.go index 13320af108b..f296502d815 100644 --- a/core/endorser/config.go +++ b/core/endorser/config.go @@ -57,10 +57,10 @@ func SetupTestConfig() { viper.AutomaticEnv() replacer := strings.NewReplacer(".", "_") viper.SetEnvKeyReplacer(replacer) - viper.SetConfigName("endorser") // name of config file (without extension) - viper.AddConfigPath("./") // path to look for the config file in - err := viper.ReadInConfig() // Find and read the config file - if err != nil { // Handle errors reading the config file + viper.SetConfigName("endorser_test") // name of config file (without extension) + viper.AddConfigPath("./") // path to look for the config file in + err := viper.ReadInConfig() // Find and read the config file + if err != nil { // Handle errors reading the config file panic(fmt.Errorf("Fatal error config file: %s \n", err)) } diff --git a/core/endorser/endorser.go b/core/endorser/endorser.go index 1b4a200172a..6ac5be9b90e 100644 --- a/core/endorser/endorser.go +++ b/core/endorser/endorser.go @@ -31,7 +31,7 @@ import ( putils "github.com/hyperledger/fabric/protos/utils" ) -var devopsLogger = logging.MustGetLogger("devops") +var devopsLogger = logging.MustGetLogger("endorser") // The Jira issue that documents Endorser flow along with its relationship to // the lifecycle chaincode - https://jira.hyperledger.org/browse/FAB-181 @@ -63,23 +63,11 @@ func (*Endorser) getTxSimulator(ledgername string) (ledger.TxSimulator, error) { return lgr.NewTxSimulator() } -//getChaincodeDeploymentSpec returns a ChaincodeDeploymentSpec given args -func (e *Endorser) getChaincodeDeploymentSpec(code []byte) (*pb.ChaincodeDeploymentSpec, error) { - cds := &pb.ChaincodeDeploymentSpec{} - - err := proto.Unmarshal(code, cds) - if err != nil { - return nil, err - } - - return cds, nil -} - //deploy the chaincode after call to the system chaincode is successful -func (e *Endorser) deploy(ctxt context.Context, chainname string, cds *pb.ChaincodeDeploymentSpec) error { +func (e *Endorser) deploy(ctxt context.Context, chainname string, cds *pb.ChaincodeDeploymentSpec, cid *pb.ChaincodeID) error { //TODO : this needs to be converted to another data structure to be handled // by the chaincode framework (which currently handles "Transaction") - t, err := pb.NewChaincodeDeployTransaction(cds, cds.ChaincodeSpec.ChaincodeID.Name) + t, err := pb.NewChaincodeDeployTransaction(cds, cid.Name) if err != nil { return err } @@ -105,8 +93,7 @@ func (e *Endorser) deploy(ctxt context.Context, chainname string, cds *pb.Chainc } //call specified chaincode (system or user) -func (e *Endorser) callChaincode(ctxt context.Context, cis *pb.ChaincodeInvocationSpec) ([]byte, []byte, *pb.ChaincodeEvent, error) { - var txsim ledger.TxSimulator +func (e *Endorser) callChaincode(ctxt context.Context, cis *pb.ChaincodeInvocationSpec, cid *pb.ChaincodeID, txsim ledger.TxSimulator) ([]byte, *pb.ChaincodeEvent, error) { var err error var b []byte var ccevent *pb.ChaincodeEvent @@ -114,17 +101,11 @@ func (e *Endorser) callChaincode(ctxt context.Context, cis *pb.ChaincodeInvocati //TODO - get chainname from cis when defined chainName := string(chaincode.DefaultChain) - if txsim, err = e.getTxSimulator(chainName); err != nil { - return nil, nil, nil, err - } - - defer txsim.Done() - ctxt = context.WithValue(ctxt, chaincode.TXSimulatorKey, txsim) - b, ccevent, err = chaincode.ExecuteChaincode(ctxt, pb.Transaction_CHAINCODE_INVOKE, chainName, cis.ChaincodeSpec.ChaincodeID.Name, cis.ChaincodeSpec.CtorMsg.Args) + b, ccevent, err = chaincode.ExecuteChaincode(ctxt, pb.Transaction_CHAINCODE_INVOKE, chainName, cid.Name, cis.ChaincodeSpec.CtorMsg.Args) if err != nil { - return nil, nil, nil, err + return nil, nil, err } //----- BEGIN - SECTION THAT MAY NEED TO BE DONE IN LCCC ------ @@ -135,29 +116,24 @@ func (e *Endorser) callChaincode(ctxt context.Context, cis *pb.ChaincodeInvocati // //NOTE that if there's an error all simulation, including the chaincode //table changes in lccc will be thrown away - if cis.ChaincodeSpec.ChaincodeID.Name == "lccc" && len(cis.ChaincodeSpec.CtorMsg.Args) == 3 && string(cis.ChaincodeSpec.CtorMsg.Args[0]) == "deploy" { + if cid.Name == "lccc" && len(cis.ChaincodeSpec.CtorMsg.Args) == 3 && string(cis.ChaincodeSpec.CtorMsg.Args[0]) == "deploy" { var cds *pb.ChaincodeDeploymentSpec - cds, err = e.getChaincodeDeploymentSpec(cis.ChaincodeSpec.CtorMsg.Args[2]) + cds, err = putils.GetChaincodeDeploymentSpec(cis.ChaincodeSpec.CtorMsg.Args[2]) if err != nil { - return nil, nil, nil, err + return nil, nil, err } - err = e.deploy(ctxt, chainName, cds) + err = e.deploy(ctxt, chainName, cds, cid) if err != nil { - return nil, nil, nil, err + return nil, nil, err } } //----- END ------- - var txSimulationResults []byte - if txSimulationResults, err = txsim.GetTxSimulationResults(); err != nil { - return nil, nil, nil, err - } - - return txSimulationResults, b, ccevent, err + return b, ccevent, err } //simulate the proposal by calling the chaincode -func (e *Endorser) simulateProposal(ctx context.Context, prop *pb.Proposal) ([]byte, []byte, *pb.ChaincodeEvent, error) { +func (e *Endorser) simulateProposal(ctx context.Context, prop *pb.Proposal, cid *pb.ChaincodeID, txsim ledger.TxSimulator) ([]byte, []byte, *pb.ChaincodeEvent, error) { //we do expect the payload to be a ChaincodeInvocationSpec //if we are supporting other payloads in future, this be glaringly point //as something that should change @@ -179,58 +155,242 @@ func (e *Endorser) simulateProposal(ctx context.Context, prop *pb.Proposal) ([]b var simResult []byte var resp []byte var ccevent *pb.ChaincodeEvent - simResult, resp, ccevent, err = e.callChaincode(ctx, cis) + resp, ccevent, err = e.callChaincode(ctx, cis, cid, txsim) if err != nil { return nil, nil, nil, err } + if simResult, err = txsim.GetTxSimulationResults(); err != nil { + return nil, nil, nil, err + } + return resp, simResult, ccevent, nil } +func (e *Endorser) getCDSFromLCCC(ctx context.Context, chaincodeID string, txsim ledger.TxSimulator) ([]byte, error) { + ctxt := context.WithValue(ctx, chaincode.TXSimulatorKey, txsim) + return chaincode.GetCDSFromLCCC(ctxt, string(chaincode.DefaultChain), chaincodeID) +} + //endorse the proposal by calling the ESCC -func (e *Endorser) endorseProposal(proposal *pb.Proposal) (*pb.Endorsement, error) { - /************ TODO - //---4. call ESCC - args := util.ToChaincodeArgs("", "serialized_action", "serialized_proposal", "any", "other", "args") - ecccis := &pb.ChaincodeInvocationSpec{ ChaincodeSpec: &pb.ChaincodeSpec{ Type: pb.ChaincodeSpec_GOLANG, ChaincodeID: &pb.ChaincodeID{ Name: "escc" }, CtorMsg: &pb.ChaincodeInput{ Args: args }}} +func (e *Endorser) endorseProposal(ctx context.Context, proposal *pb.Proposal, simRes []byte, event *pb.ChaincodeEvent, visibility []byte, ccid *pb.ChaincodeID, txsim ledger.TxSimulator) ([]byte, error) { + devopsLogger.Infof("endorseProposal starts for proposal %p, simRes %p event %p, visibility %p, ccid %s", proposal, simRes, event, visibility, ccid) + + // 1) extract the chaincodeDeploymentSpec for the chaincode we are invoking; we need it to get the escc + var escc string + if ccid.Name != "lccc" { + depPayload, err := e.getCDSFromLCCC(ctx, ccid.Name, txsim) + if err != nil { + return nil, fmt.Errorf("failed to obtain cds for %s - %s", ccid, err) + } - var sig []byte - sig, err = e.callChaincode(ecccis) + _, err = putils.GetChaincodeDeploymentSpec(depPayload) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal cds for %s - %s", ccid, err) + } + + // FIXME: pick the right escc from cds - currently cds doesn't have this info + escc = "escc" + } else { + // FIXME: getCDSFromLCCC seems to fail for lccc - not sure this is expected? + escc = "escc" + } + + devopsLogger.Infof("endorseProposal info: escc for cid %s is %s", ccid, escc) + + // marshalling event bytes + var err error + var eventBytes []byte = nil + if event != nil { + eventBytes, err = putils.GetBytesChaincodeEvent(event) + if err != nil { + return nil, fmt.Errorf("failed to marshal event bytes - %s", err) + } + } + + // 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] - binary blob of simulation results + // args[4] - serialized events + // args[5] - payloadVisibility + args := [][]byte{[]byte(""), proposal.Header, proposal.Payload, simRes, eventBytes, visibility} + ecccis := &pb.ChaincodeInvocationSpec{ChaincodeSpec: &pb.ChaincodeSpec{Type: pb.ChaincodeSpec_GOLANG, ChaincodeID: &pb.ChaincodeID{Name: escc}, CtorMsg: &pb.ChaincodeInput{Args: args}}} + prBytes, _, err := e.callChaincode(ctx, ecccis, &pb.ChaincodeID{Name: escc}, txsim) if err != nil { - return err + return nil, err + } + + // 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. + + return prBytes, nil +} + +// FIXME: this method might be of general interest, should we package it somewhere else? +// validateChaincodeProposalMessage checks the validity of a CHAINCODE Proposal message +func (e *Endorser) validateChaincodeProposalMessage(prop *pb.Proposal, hdr *pb.Header) (*pb.ChaincodeHeaderExtension, error) { + devopsLogger.Infof("validateChaincodeProposalMessage starts for proposal %p, header %p", prop, hdr) + + // 4) based on the header type (assuming it's CHAINCODE), look at the extensions + chaincodeHdrExt, err := putils.GetChaincodeHeaderExtension(hdr) + if err != nil { + return nil, fmt.Errorf("Invalid header extension for type CHAINCODE") + } + + devopsLogger.Infof("validateChaincodeProposalMessage info: header extension references chaincode %s", chaincodeHdrExt.ChaincodeID) + + // - ensure that the chaincodeID is correct (?) + // TODO: should we even do this? If so, using which interface? + + // - ensure that the visibility field has some value we understand + // TODO: we need to define visibility fields first + + // TODO: should we check the payload as well? + + return chaincodeHdrExt, nil +} + +// FIXME: this method might be of general interest, should we package it somewhere else? +// validateProposalMessage checks the validity of a generic Proposal message +// this function returns Header and ChaincodeHeaderExtension messages since they +// have been unmarshalled and validated +func (e *Endorser) validateProposalMessage(prop *pb.Proposal) (*pb.Header, *pb.ChaincodeHeaderExtension, error) { + devopsLogger.Infof("validateProposalMessage starts for proposal %p", prop) + + // 1) look at the ProposalHeader + hdr, err := putils.GetHeader(prop) + if err != nil { + return nil, nil, err + } + + // - validate the type + if hdr.Type != pb.Header_CHAINCODE { + return nil, nil, fmt.Errorf("Invalid proposal type %d", hdr.Type) + } + + devopsLogger.Infof("validateProposalMessage info: proposal type %d", hdr.Type) + + // - ensure that there is a nonce and a creator + if hdr.Nonce == nil || len(hdr.Nonce) == 0 { + return nil, nil, fmt.Errorf("Invalid nonce specified in the header") + } + if hdr.Creator == nil || len(hdr.Creator) == 0 { + return nil, nil, fmt.Errorf("Invalid creator specified in the header") + } + + // - ensure that creator is a valid certificate (depends on membership svc) + // TODO: We need MSP APIs for this + + // - ensure that creator is trusted (signed by a trusted CA) + // TODO: We need MSP APIs for this + + // - ensure that creator can transact with us (some ACLs?) + // TODO: which set of APIs is supposed to give us this info? + + // - ensure that the version is what we expect + // TODO: Which is the right version? + + // - ensure that the chainID is valid + // TODO: which set of APIs is supposed to give us this info? + + // 2) perform a check against replay attacks + // TODO + + // 3) validate the signature of creator on header and payload + // TODO: We need MSP APIs for this + + // validation of the proposal message knowing it's of type CHAINCODE + chaincodeHdrExt, err := e.validateChaincodeProposalMessage(prop, hdr) + if err != nil { + return nil, nil, err } - ************/ - endorsement := &pb.Endorsement{Signature: []byte("TODO Signature")} - return endorsement, nil + return hdr, chaincodeHdrExt, err } // ProcessProposal process the Proposal func (e *Endorser) ProcessProposal(ctx context.Context, prop *pb.Proposal) (*pb.ProposalResponse, error) { + // at first, we check whether the message is valid + // TODO: Do the checks performed by this function belong here or in the ESCC? From a security standpoint they should be performed as early as possible so here seems to be a good place + _, hdrExt, err := e.validateProposalMessage(prop) + if err != nil { + return &pb.ProposalResponse{Response: &pb.Response2{Status: 500, Message: err.Error()}}, err + } + + // obtaining once the tx simulator for this proposal + var txsim ledger.TxSimulator + //TODO - get chainname from the proposal when defined + chainName := string(chaincode.DefaultChain) + if txsim, err = e.getTxSimulator(chainName); err != nil { + return &pb.ProposalResponse{Response: &pb.Response2{Status: 500, Message: err.Error()}}, err + } + defer txsim.Done() + + // TODO: if the proposal has an extension, it will be of type ChaincodeAction; + // if it's present it means that no simulation is to be performed because + // we're trying to emulate a submitting peer. On the other hand, we need + // to validate the supplied action before endorsing it + //1 -- simulate //TODO what do we do with response ? We need it for Invoke responses for sure //Which field in PayloadResponse will carry return value ? - payload, simulationResult, ccevent, err := e.simulateProposal(ctx, prop) + _, simulationResult, ccevent, err := e.simulateProposal(ctx, prop, hdrExt.ChaincodeID, txsim) if err != nil { return &pb.ProposalResponse{Response: &pb.Response2{Status: 500, Message: err.Error()}}, err } - //2 -- endorse + //2 -- endorse and get a marshalled ProposalResponse message //TODO what do we do with response ? We need it for Invoke responses for sure - endorsement, err := e.endorseProposal(prop) + prBytes, err := e.endorseProposal(ctx, prop, simulationResult, ccevent, hdrExt.PayloadVisibility, hdrExt.ChaincodeID, txsim) if err != nil { return &pb.ProposalResponse{Response: &pb.Response2{Status: 500, Message: err.Error()}}, err } //3 -- respond - // Create action - pResp, err := putils.CreateProposalResponse(pb.Header_CHAINCODE, prop, ccevent, simulationResult, endorsement) + pResp, err := putils.GetProposalResponse(prBytes) if err != nil { return nil, err } - //TODO when we have additional field in response, use "resp" bytes from the simulation - pResp.Response = &pb.Response2{Status: 200, Message: "Proposal accepted", Payload: payload} - return pResp, nil } + +// Only exposed for testing purposes - commit the tx simulation so that +// a deploy transaction is persisted and that chaincode can be invoked. +// This makes the endorser test self-sufficient +func (e *Endorser) commitTxSimulation(pResp *pb.ProposalResponse) error { + tx, err := putils.CreateTxFromProposalResponse(pResp) + if err != nil { + return err + } + + ledgername := string(chaincode.DefaultChain) + lgr := kvledger.GetLedger(ledgername) + if lgr == nil { + return fmt.Errorf("failure while looking up the ledger") + } + + txBytes, err := proto.Marshal(tx) + if err != nil { + return err + } + block := &pb.Block2{Transactions: [][]byte{txBytes}} + if _, _, err = lgr.RemoveInvalidTransactionsAndPrepare(block); err != nil { + return err + } + + if err = lgr.Commit(); err != nil { + return err + } + + return nil +} diff --git a/core/endorser/endorser_test.go b/core/endorser/endorser_test.go index b7c34d35ffa..92c7134dcd7 100644 --- a/core/endorser/endorser_test.go +++ b/core/endorser/endorser_test.go @@ -29,9 +29,11 @@ import ( "github.com/hyperledger/fabric/core/chaincode" "github.com/hyperledger/fabric/core/container" "github.com/hyperledger/fabric/core/crypto" + "github.com/hyperledger/fabric/core/crypto/primitives" "github.com/hyperledger/fabric/core/db" "github.com/hyperledger/fabric/core/ledger/kvledger" "github.com/hyperledger/fabric/core/peer" + "github.com/hyperledger/fabric/core/util" pb "github.com/hyperledger/fabric/protos" pbutils "github.com/hyperledger/fabric/protos/utils" "github.com/spf13/viper" @@ -80,6 +82,13 @@ func initPeer() (net.Listener, error) { var secHelper crypto.Peer if viper.GetBool("security.enabled") { //TODO: integrate new crypto / idp + securityLevel := viper.GetInt("security.level") + hashAlgorithm := viper.GetString("security.hashAlgorithm") + primitives.SetSecurityLevel(hashAlgorithm, securityLevel) + } else { + // the primitives need to be instantiated no matter what. Otherwise + // the escc code won't have a hash algorithm available to hash the proposal + primitives.SetSecurityLevel("SHA2", 256) } ccStartupTimeout := time.Duration(30000) * time.Millisecond @@ -112,7 +121,7 @@ func closeListenerAndSleep(l net.Listener) { //getProposal gets the proposal for the chaincode invocation //Currently supported only for Invokes (Queries still go through devops client) func getProposal(cis *pb.ChaincodeInvocationSpec) (*pb.Proposal, error) { - return pbutils.CreateChaincodeProposal(cis) + return pbutils.CreateChaincodeProposal(cis, []byte("cert")) } //getDeployProposal gets the proposal for the chaincode deployment @@ -165,6 +174,23 @@ func deploy(endorserServer pb.EndorserServer, spec *pb.ChaincodeSpec, f func(*pb return resp, err } +func invoke(spec *pb.ChaincodeSpec) (*pb.ProposalResponse, error) { + invocation := &pb.ChaincodeInvocationSpec{ChaincodeSpec: spec} + + var prop *pb.Proposal + prop, err := getProposal(invocation) + if err != nil { + return nil, fmt.Errorf("Error creating proposal %s: %s\n", spec.ChaincodeID, err) + } + + resp, err := endorserServer.ProcessProposal(context.Background(), prop) + if err != nil { + return nil, fmt.Errorf("Error endorsing %s: %s\n", spec.ChaincodeID, err) + } + + return resp, err +} + //begin tests. Note that we rely upon the system chaincode and peer to be created //once and be used for all the tests. In order to avoid dependencies / collisions //due to deployed chaincodes, trying to use different chaincodes for different @@ -242,6 +268,49 @@ func TestRedeploy(t *testing.T) { chaincode.GetChain(chaincode.DefaultChain).Stop(context.Background(), &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec}) } +// TestDeployAndInvoke deploys and invokes chaincode_example01 +func TestDeployAndInvoke(t *testing.T) { + var ctxt = context.Background() + + url := "github.com/hyperledger/fabric/examples/chaincode/go/chaincode_example01" + chaincodeID := &pb.ChaincodeID{Path: url, Name: "ex01"} + + args := []string{"10"} + + f := "init" + argsDeploy := util.ToChaincodeArgs(f, "a", "100", "b", "200") + spec := &pb.ChaincodeSpec{Type: 1, ChaincodeID: chaincodeID, CtorMsg: &pb.ChaincodeInput{Args: argsDeploy}} + resp, err := deploy(endorserServer, spec, nil) + chaincodeID1 := spec.ChaincodeID.Name + if err != nil { + t.Fail() + t.Logf("Error deploying <%s>: %s", chaincodeID1, err) + return + } + + err = endorserServer.(*Endorser).commitTxSimulation(resp) + if err != nil { + t.Fail() + t.Logf("Error committing <%s>: %s", chaincodeID1, err) + return + } + + f = "invoke" + invokeArgs := append([]string{f}, args...) + spec = &pb.ChaincodeSpec{Type: 1, ChaincodeID: chaincodeID, CtorMsg: &pb.ChaincodeInput{Args: util.ToChaincodeArgs(invokeArgs...)}} + resp, err = invoke(spec) + if err != nil { + t.Fail() + t.Logf("Error invoking transaction: %s", err) + return + } else { + fmt.Printf("Invoke test passed\n") + t.Logf("Invoke test passed") + } + + chaincode.GetChain(chaincode.DefaultChain).Stop(ctxt, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: &pb.ChaincodeSpec{ChaincodeID: chaincodeID}}) +} + func TestMain(m *testing.M) { SetupTestConfig() testDBWrapper.CleanDB(nil) diff --git a/core/endorser/endorser.yaml b/core/endorser/endorser_test.yaml similarity index 99% rename from core/endorser/endorser.yaml rename to core/endorser/endorser_test.yaml index b203770ebcb..03c572faf62 100644 --- a/core/endorser/endorser.yaml +++ b/core/endorser/endorser_test.yaml @@ -22,6 +22,10 @@ security: # Must be the same as in core.yaml level: 256 + # Can be SHA2 or SHA3 + # Must be the same as in core.yaml + hashAlgorithm: SHA3 + # Enabling/disabling different logging levels of the CA. # logging: diff --git a/core/system_chaincode/escc/endorser_onevalidsignature.go b/core/system_chaincode/escc/endorser_onevalidsignature.go index ec9a4d46a6c..583d0278e9e 100644 --- a/core/system_chaincode/escc/endorser_onevalidsignature.go +++ b/core/system_chaincode/escc/endorser_onevalidsignature.go @@ -19,10 +19,16 @@ package escc import ( "errors" + "fmt" + "github.com/hyperledger/fabric/core/chaincode/shim" - //"github.com/hyperledger/fabric/core/crypto" + "github.com/hyperledger/fabric/protos" + "github.com/hyperledger/fabric/protos/utils" + "github.com/op/go-logging" ) +var logger = logging.MustGetLogger("escc") + // EndorserOneValidSignature implements the default endorsement policy, which is to // sign the proposal hash and the read-write set type EndorserOneValidSignature struct { @@ -39,26 +45,102 @@ func (e *EndorserOneValidSignature) Init(stub shim.ChaincodeStubInterface) ([]by // 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 signature of Action object or error -// Note that Peer calls this function with 3 arguments, where args[0] is the -// function name and args[1] is the Action object and args[2] is Proposal object +// @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] - binary blob of simulation results +// args[4] - serialized events (optional) +// args[5] - payloadVisibility (optional) +// +// 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) ([]byte, error) { - // args[0] - function name (not used now) - // args[1] - serialized Action object - // args[2] - serialized Proposal object (not used) args := stub.GetArgs() - if len(args) < 3 { - return nil, errors.New("Incorrect number of arguments") + if len(args) < 4 { + return nil, fmt.Errorf("Incorrect number of arguments (expected a minimum of 4, provided %d)", len(args)) + } else if len(args) > 6 { + return nil, fmt.Errorf("Incorrect number of arguments (expected a maximum of 6, provided %d)", len(args)) } + logger.Infof("ESCC starts: %d args", len(args)) + + // handle the header + var hdr []byte if args[1] == nil { - return nil, errors.New("Action object is null") + return nil, errors.New("serialized Header object is null") + } else { + hdr = args[1] + } + + // handle the proposal payload + var payl []byte + if args[2] == nil { + return nil, errors.New("serialized ChaincodeProposalPayload object is null") + } else { + payl = args[2] + } + + // handle simulation results + var results []byte + if args[3] == nil { + return nil, errors.New("simulation results are null") + } else { + results = args[3] + } + + // 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) > 4 && args[4] != nil { + events = args[4] + } + + // Handle payload visibility (it's an optional argument) + visibility := []byte("") // TODO: when visibility is properly defined, replace with the default + if len(args) > 5 { + if args[5] == nil { + return nil, errors.New("serialized events are null") + } else { + visibility = args[5] + } + } + + // obtain the proposal hash given proposal header, payload and the requested visibility + pHashBytes, err := utils.GetProposalHash(hdr, payl, visibility) + if err != nil { + return nil, fmt.Errorf("Could not compute proposal hash: err %s", err) + } + + // TODO: obtain current epoch + epoch := []byte("current_epoch") + logger.Infof("using epoch %s", string(epoch)) + + // get the bytes of the proposal response payload - we need to sign them + prpBytes, err := utils.GetBytesProposalResponsePayload(pHashBytes, epoch, results, events) + if err != nil { + return nil, errors.New("Failure while unmarshalling the ProposalResponsePayload") + } + + // TODO: obtain the signing key for this endorser - what API should be used? + endorser := []byte("here_goes_the_endorsers_key") + + // TODO: sign prpBytes with this endorser's key - use msp interfaces and providers + signature := []byte("here_goes_the_signature_of_prpBytes_under_the_endorsers_key") + + // marshall the proposal response so that we return its bytes + prBytes, err := utils.GetBytesProposalResponse(prpBytes, &protos.Endorsement{Signature: signature, Endorser: endorser}) + if err != nil { + return nil, fmt.Errorf("Could not marshall ProposalResponse: err %s", err) } - // TODO: since we are just trying to get the end-to-end happy path going, we return a fake signed proposal - // Once the scc interface is updated to have a pointer to the peer SecHelper, we will compute the actual signature - return []byte("true"), nil - //return crypto.sign(args[1]) + logger.Infof("ESCC exits successfully") + return prBytes, nil } // Query is here to satisfy the Chaincode interface. We don't need it for this system chaincode diff --git a/core/system_chaincode/escc/endorser_onevalidsignature_test.go b/core/system_chaincode/escc/endorser_onevalidsignature_test.go index 41fc3e3eedf..6825e95e110 100644 --- a/core/system_chaincode/escc/endorser_onevalidsignature_test.go +++ b/core/system_chaincode/escc/endorser_onevalidsignature_test.go @@ -19,12 +19,16 @@ import ( "fmt" "testing" - "github.com/golang/protobuf/proto" + "bytes" + "github.com/hyperledger/fabric/core/chaincode/shim" + "github.com/hyperledger/fabric/core/crypto/primitives" pb "github.com/hyperledger/fabric/protos" + putils "github.com/hyperledger/fabric/protos/utils" ) func TestInit(t *testing.T) { + primitives.InitSecurityLevel("SHA2", 256) e := new(EndorserOneValidSignature) stub := shim.NewMockStub("endorseronevalidsignature", e) @@ -44,28 +48,164 @@ func TestInvoke(t *testing.T) { 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 _, err := stub.MockInvoke("1", args); err == nil { + 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 _, err := stub.MockInvoke("1", args); err == nil { + t.Fatalf("escc invoke should have failed with invalid number of args: %v", args) + } + + // Failed path: header is null + args = [][]byte{[]byte("test"), nil, []byte("test"), []byte("test")} + if _, err := stub.MockInvoke("1", args); err == nil { + fmt.Println("Invoke", args, "failed", err) + 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, []byte("test")} + if _, err := stub.MockInvoke("1", args); err == nil { + fmt.Println("Invoke", args, "failed", err) + t.Fatalf("escc invoke should have failed with a null payload. args: %v", args) + } + // Failed path: action struct is null - args = [][]byte{[]byte("test"), []byte("action"), []byte("proposal")} - args[1] = nil + args = [][]byte{[]byte("test"), []byte("test"), []byte("test"), nil} if _, err := stub.MockInvoke("1", args); err == nil { fmt.Println("Invoke", args, "failed", err) - t.Fatalf("escc invoke should have failed with Action object is null. args: %v", args) + t.Fatalf("escc invoke should have failed with a null action struct. args: %v", args) + } + + // Successful path - create a proposal + cs := &pb.ChaincodeSpec{ + ChaincodeID: &pb.ChaincodeID{Name: "foo"}, + Type: pb.ChaincodeSpec_GOLANG, + CtorMsg: &pb.ChaincodeInput{Args: [][]byte{[]byte("some"), []byte("args")}}} + + cis := &pb.ChaincodeInvocationSpec{ChaincodeSpec: cs} + + proposal, err := putils.CreateChaincodeProposal(cis, []byte("creator_tcert")) + if err != nil { + t.Fail() + t.Fatalf("couldn't generate chaincode proposal: err %s", err) + return } - // Successful path - args = [][]byte{[]byte("dv"), mockAction(), mockProposal()} - if _, err := stub.MockInvoke("1", args); err != nil { + // success test 1: invocation with mandatory args only + simRes := []byte("simulation_result") + + args = [][]byte{[]byte(""), proposal.Header, proposal.Payload, simRes} + prBytes, err := stub.MockInvoke("1", args) + if err != nil { + t.Fail() t.Fatalf("escc invoke failed with: %v", err) + return } - // TODO: Check sig here when we actually sign -} -func mockAction() []byte { - action := &pb.ChaincodeAction{} - action.Results = []byte("read-write set") - payload, _ := proto.Marshal(action) - return payload + err = validateProposalResponse(prBytes, proposal, nil, 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, simRes, events} + prBytes, err = stub.MockInvoke("1", args) + if err != nil { + t.Fail() + t.Fatalf("escc invoke failed with: %v", err) + return + } + + err = validateProposalResponse(prBytes, proposal, nil, simRes, events) + if err != nil { + t.Fail() + t.Fatalf("%s", err) + return + } + + // success test 3: invocation with mandatory args + events + visibility := []byte("visibility") + + args = [][]byte{[]byte(""), proposal.Header, proposal.Payload, simRes, events, visibility} + prBytes, err = stub.MockInvoke("1", args) + if err != nil { + t.Fail() + t.Fatalf("escc invoke failed with: %v", err) + return + } + + err = validateProposalResponse(prBytes, proposal, visibility, simRes, events) + if err != nil { + t.Fail() + t.Fatalf("%s", err) + return + } } -func mockProposal() []byte { - return []byte("proposal") + +func validateProposalResponse(prBytes []byte, proposal *pb.Proposal, visibility []byte, 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 + + // recompute proposal hash + pHash, err := putils.GetProposalHash(proposal.Header, 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 fmt.Errorf("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 results match + if bytes.Compare(cact.Results, simRes) != 0 { + return fmt.Errorf("results do not match") + } + + // validate that the events match + if bytes.Compare(cact.Events, events) != 0 { + return fmt.Errorf("events do not match") + } + + // TODO: check the endorsement: pResp.Endorsement.Signature is supposed to be a signature of pResp.Payload with the key specified in pResp.Endorsement.Endorser + return nil } diff --git a/peer/chaincode/common.go b/peer/chaincode/common.go index 23f01d7f4f5..521d1c3a230 100755 --- a/peer/chaincode/common.go +++ b/peer/chaincode/common.go @@ -37,13 +37,13 @@ import ( //getProposal gets the proposal for the chaincode invocation //Currently supported only for Invokes (Queries still go through devops client) -func getProposal(cis *pb.ChaincodeInvocationSpec) (*pb.Proposal, error) { - return putils.CreateChaincodeProposal(cis) +func getProposal(cis *pb.ChaincodeInvocationSpec, creator []byte) (*pb.Proposal, error) { + return putils.CreateChaincodeProposal(cis, creator) } //getDeployProposal gets the proposal for the chaincode deployment //the payload is a ChaincodeDeploymentSpec -func getDeployProposal(cds *pb.ChaincodeDeploymentSpec) (*pb.Proposal, error) { +func getDeployProposal(cds *pb.ChaincodeDeploymentSpec, creator []byte) (*pb.Proposal, error) { b, err := proto.Marshal(cds) if err != nil { return nil, err @@ -53,7 +53,7 @@ func getDeployProposal(cds *pb.ChaincodeDeploymentSpec) (*pb.Proposal, error) { lcccSpec := &pb.ChaincodeInvocationSpec{ChaincodeSpec: &pb.ChaincodeSpec{Type: pb.ChaincodeSpec_GOLANG, ChaincodeID: &pb.ChaincodeID{Name: "lccc"}, CtorMsg: &pb.ChaincodeInput{Args: [][]byte{[]byte("deploy"), []byte("default"), b}}}} //...and get the proposal for it - return getProposal(lcccSpec) + return getProposal(lcccSpec, creator) } func getChaincodeSpecification(cmd *cobra.Command) (*pb.ChaincodeSpec, error) { @@ -155,7 +155,8 @@ func chaincodeInvokeOrQuery(cmd *cobra.Command, args []string, invoke bool) (err } var prop *pb.Proposal - prop, err = getProposal(invocation) + // TODO: how should we get a cert from the command line? + prop, err = getProposal(invocation, []byte("cert")) if err != nil { return fmt.Errorf("Error creating proposal %s: %s\n", chainFuncName, err) } diff --git a/peer/chaincode/deploy.go b/peer/chaincode/deploy.go index 63452955d70..df1fdc7cb2b 100755 --- a/peer/chaincode/deploy.go +++ b/peer/chaincode/deploy.go @@ -61,7 +61,8 @@ func deploy(cmd *cobra.Command) (*pb.ProposalResponse, error) { return nil, fmt.Errorf("Error getting endorser client %s: %s", chainFuncName, err) } - prop, err := getDeployProposal(cds) + // TODO: how should we get a cert from the command line? + prop, err := getDeployProposal(cds, []byte("cert")) if err != nil { return nil, fmt.Errorf("Error creating proposal %s: %s\n", chainFuncName, err) } diff --git a/peer/node/start.go b/peer/node/start.go index ad125e479c0..cab5879c205 100755 --- a/peer/node/start.go +++ b/peer/node/start.go @@ -34,6 +34,7 @@ import ( "github.com/hyperledger/fabric/core/comm" "github.com/hyperledger/fabric/core/committer/noopssinglechain" "github.com/hyperledger/fabric/core/crypto" + "github.com/hyperledger/fabric/core/crypto/primitives" "github.com/hyperledger/fabric/core/db" "github.com/hyperledger/fabric/core/endorser" "github.com/hyperledger/fabric/core/peer" @@ -357,5 +358,8 @@ var once sync.Once //and universally accessed func getSecHelper() (crypto.Peer, error) { //TODO: integrated new crypto / idp code + once.Do(func() { + primitives.SetSecurityLevel("SHA2", 256) + }) return nil, nil } diff --git a/protos/chaincode_proposal.pb.go b/protos/chaincode_proposal.pb.go index f1bc2040e24..4f63f42837f 100644 --- a/protos/chaincode_proposal.pb.go +++ b/protos/chaincode_proposal.pb.go @@ -30,7 +30,7 @@ type ChaincodeHeaderExtension struct { // this field impacts the content of ProposalResponsePayload.proposalHash. PayloadVisibility []byte `protobuf:"bytes,1,opt,name=payloadVisibility,proto3" json:"payloadVisibility,omitempty"` // The ID of the chaincode to target. - ChaincodeID []byte `protobuf:"bytes,2,opt,name=chaincodeID,proto3" json:"chaincodeID,omitempty"` + ChaincodeID *ChaincodeID `protobuf:"bytes,2,opt,name=chaincodeID" json:"chaincodeID,omitempty"` } func (m *ChaincodeHeaderExtension) Reset() { *m = ChaincodeHeaderExtension{} } @@ -38,6 +38,13 @@ func (m *ChaincodeHeaderExtension) String() string { return proto.Com func (*ChaincodeHeaderExtension) ProtoMessage() {} func (*ChaincodeHeaderExtension) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{0} } +func (m *ChaincodeHeaderExtension) GetChaincodeID() *ChaincodeID { + if m != nil { + return m.ChaincodeID + } + return nil +} + // ChaincodeProposalPayload is the Proposal's payload message to be used when // the Header's type is CHAINCODE. It contains the arguments for this // invocation. @@ -82,20 +89,20 @@ func init() { func init() { proto.RegisterFile("chaincode_proposal.proto", fileDescriptor2) } var fileDescriptor2 = []byte{ - // 236 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x64, 0x90, 0x41, 0x4b, 0x03, 0x31, - 0x10, 0x85, 0xa9, 0x60, 0xc5, 0x51, 0x10, 0x83, 0x48, 0x0e, 0x1e, 0xca, 0x22, 0xe2, 0x41, 0xba, - 0x07, 0x7f, 0x81, 0x56, 0xc1, 0x5e, 0xa4, 0x88, 0x78, 0xf0, 0x22, 0xd9, 0xec, 0xd8, 0x1d, 0x89, - 0x49, 0xc8, 0xcc, 0x8a, 0xfb, 0xef, 0xc5, 0x26, 0xad, 0x05, 0x4f, 0xe1, 0x4d, 0x1e, 0xdf, 0x7b, - 0x3c, 0xd0, 0xb6, 0x33, 0xe4, 0x6d, 0x68, 0xf1, 0x2d, 0xa6, 0x10, 0x03, 0x1b, 0x37, 0x8d, 0x29, - 0x48, 0x50, 0xe3, 0xd5, 0xc3, 0xd5, 0x07, 0xe8, 0xd9, 0xda, 0xf3, 0x80, 0xa6, 0xc5, 0x74, 0xff, - 0x2d, 0xe8, 0x99, 0x82, 0x57, 0x57, 0x70, 0x1c, 0xcd, 0xe0, 0x82, 0x69, 0x5f, 0x88, 0xa9, 0x21, - 0x47, 0x32, 0xe8, 0xd1, 0x64, 0x74, 0x79, 0xf8, 0xf4, 0xff, 0x43, 0x4d, 0xe0, 0x60, 0x93, 0x36, - 0xbf, 0xd3, 0x3b, 0x2b, 0xdf, 0xf6, 0xa9, 0x7a, 0xdc, 0xca, 0x5a, 0x94, 0x3a, 0x8b, 0xcc, 0x51, - 0x27, 0xb0, 0x3b, 0xf7, 0xb1, 0x97, 0xc2, 0xcf, 0x42, 0x9d, 0xc1, 0xfe, 0x73, 0x32, 0x9e, 0x09, - 0xbd, 0x14, 0xe2, 0xdf, 0xa1, 0x9a, 0xc1, 0xd1, 0x86, 0x77, 0x63, 0xe5, 0xb7, 0xb2, 0x86, 0xbd, - 0x84, 0xdc, 0x3b, 0xe1, 0x02, 0x5a, 0x4b, 0x75, 0x0a, 0x63, 0xfc, 0x42, 0x2f, 0x5c, 0x38, 0x45, - 0xdd, 0x5e, 0xbc, 0x9e, 0x2f, 0x49, 0xba, 0xbe, 0x99, 0xda, 0xf0, 0x59, 0x77, 0x43, 0xc4, 0xe4, - 0xb0, 0x5d, 0x62, 0xaa, 0xdf, 0x4d, 0x93, 0xc8, 0xd6, 0x79, 0xa8, 0x26, 0x0f, 0x76, 0xfd, 0x13, - 0x00, 0x00, 0xff, 0xff, 0xeb, 0x2e, 0x0e, 0xd0, 0x53, 0x01, 0x00, 0x00, + // 225 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0x92, 0x48, 0xce, 0x48, 0xcc, + 0xcc, 0x4b, 0xce, 0x4f, 0x49, 0x8d, 0x2f, 0x28, 0xca, 0x2f, 0xc8, 0x2f, 0x4e, 0xcc, 0xd1, 0x2b, + 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x03, 0x53, 0xc5, 0x52, 0xfc, 0x70, 0x15, 0x10, 0x09, 0xa5, + 0x7a, 0x2e, 0x09, 0x67, 0x98, 0x90, 0x47, 0x6a, 0x62, 0x4a, 0x6a, 0x91, 0x6b, 0x45, 0x49, 0x6a, + 0x5e, 0x71, 0x66, 0x7e, 0x9e, 0x90, 0x0e, 0x97, 0x60, 0x41, 0x62, 0x65, 0x4e, 0x7e, 0x62, 0x4a, + 0x58, 0x66, 0x71, 0x66, 0x52, 0x66, 0x4e, 0x66, 0x49, 0xa5, 0x04, 0xa3, 0x02, 0xa3, 0x06, 0x4f, + 0x10, 0xa6, 0x84, 0x90, 0x29, 0x17, 0x37, 0xdc, 0x70, 0x4f, 0x17, 0x09, 0x26, 0x05, 0x46, 0x0d, + 0x6e, 0x23, 0x61, 0x88, 0x35, 0xc5, 0x7a, 0xce, 0x08, 0xa9, 0x20, 0x64, 0x75, 0x4a, 0x7e, 0x48, + 0x0e, 0x08, 0x80, 0x3a, 0x3a, 0x00, 0x62, 0xb8, 0x90, 0x08, 0x17, 0xab, 0x67, 0x5e, 0x41, 0x69, + 0x09, 0xd4, 0x52, 0x08, 0x47, 0x48, 0x86, 0x8b, 0x33, 0xa4, 0x28, 0x31, 0xaf, 0x38, 0x33, 0x35, + 0xaf, 0x04, 0x6c, 0x0d, 0x4f, 0x10, 0x42, 0x40, 0xc9, 0x99, 0x8b, 0x1f, 0x6e, 0x9e, 0x63, 0x72, + 0x09, 0xc8, 0x1f, 0x12, 0x5c, 0xec, 0x45, 0xa9, 0xc5, 0xa5, 0x39, 0x25, 0xc5, 0x50, 0x83, 0x60, + 0x5c, 0x21, 0x31, 0x2e, 0xb6, 0xd4, 0xb2, 0xd4, 0xbc, 0x92, 0x62, 0xa8, 0x39, 0x50, 0x5e, 0x12, + 0x24, 0xb8, 0x8c, 0x01, 0x01, 0x00, 0x00, 0xff, 0xff, 0x40, 0x10, 0x2e, 0xce, 0x51, 0x01, 0x00, + 0x00, } diff --git a/protos/chaincode_proposal.proto b/protos/chaincode_proposal.proto index 7df4e1a12dc..b1d869ff54d 100644 --- a/protos/chaincode_proposal.proto +++ b/protos/chaincode_proposal.proto @@ -20,6 +20,8 @@ option go_package = "github.com/hyperledger/fabric/protos"; package protos; +import "chaincode.proto"; + /* The flow to get a CHAINCODE transaction approved goes as follows: @@ -102,7 +104,7 @@ message ChaincodeHeaderExtension { bytes payloadVisibility = 1; // The ID of the chaincode to target. - bytes chaincodeID = 2; + ChaincodeID chaincodeID = 2; } // ChaincodeProposalPayload is the Proposal's payload message to be used when diff --git a/protos/utils/proputils.go b/protos/utils/proputils.go index 934ad367409..84671edba40 100644 --- a/protos/utils/proputils.go +++ b/protos/utils/proputils.go @@ -17,9 +17,11 @@ limitations under the License. package utils import ( + "errors" "fmt" "github.com/golang/protobuf/proto" + "github.com/hyperledger/fabric/core/crypto/primitives" "github.com/hyperledger/fabric/protos" ) @@ -46,9 +48,70 @@ func GetChaincodeInvocationSpec(prop *protos.Proposal) (*protos.ChaincodeInvocat return cis, nil } +func GetHeader(prop *protos.Proposal) (*protos.Header, error) { + hdr := &protos.Header{} + err := proto.Unmarshal(prop.Header, hdr) + if err != nil { + return nil, err + } + + return hdr, nil +} + +func GetChaincodeHeaderExtension(hdr *protos.Header) (*protos.ChaincodeHeaderExtension, error) { + chaincodeHdrExt := &protos.ChaincodeHeaderExtension{} + err := proto.Unmarshal(hdr.Extensions, chaincodeHdrExt) + if err != nil { + return nil, err + } + + return chaincodeHdrExt, nil +} + +func GetProposalResponse(prBytes []byte) (*protos.ProposalResponse, error) { + proposalResponse := &protos.ProposalResponse{} + err := proto.Unmarshal(prBytes, proposalResponse) + if err != nil { + return nil, err + } + + return proposalResponse, nil +} + +//getChaincodeDeploymentSpec returns a ChaincodeDeploymentSpec given args +func GetChaincodeDeploymentSpec(code []byte) (*protos.ChaincodeDeploymentSpec, error) { + cds := &protos.ChaincodeDeploymentSpec{} + err := proto.Unmarshal(code, cds) + if err != nil { + return nil, err + } + + return cds, nil +} + +func GetChaincodeAction(caBytes []byte) (*protos.ChaincodeAction, error) { + chaincodeAction := &protos.ChaincodeAction{} + err := proto.Unmarshal(caBytes, chaincodeAction) + if err != nil { + return nil, err + } + + return chaincodeAction, nil +} + +func GetProposalResponsePayload(prpBytes []byte) (*protos.ProposalResponsePayload, error) { + prp := &protos.ProposalResponsePayload{} + err := proto.Unmarshal(prpBytes, prp) + if err != nil { + return nil, err + } + + return prp, nil +} + // CreateChaincodeProposal creates a proposal from given input -func CreateChaincodeProposal(cis *protos.ChaincodeInvocationSpec) (*protos.Proposal, error) { - ccHdrExt := &protos.ChaincodeHeaderExtension{} +func CreateChaincodeProposal(cis *protos.ChaincodeInvocationSpec, creator []byte) (*protos.Proposal, error) { + ccHdrExt := &protos.ChaincodeHeaderExtension{ChaincodeID: cis.ChaincodeSpec.ChaincodeID} ccHdrExtBytes, err := proto.Marshal(ccHdrExt) if err != nil { return nil, err @@ -65,7 +128,16 @@ func CreateChaincodeProposal(cis *protos.ChaincodeInvocationSpec) (*protos.Propo return nil, err } - hdr := &protos.Header{Type: protos.Header_CHAINCODE, Extensions: ccHdrExtBytes} + // generate a random nonce + nonce, err := primitives.GetRandomNonce() + if err != nil { + return nil, err + } + + hdr := &protos.Header{Type: protos.Header_CHAINCODE, + Extensions: ccHdrExtBytes, + Nonce: nonce, + Creator: creator} hdrBytes, err := proto.Marshal(hdr) if err != nil { return nil, err @@ -74,36 +146,80 @@ func CreateChaincodeProposal(cis *protos.ChaincodeInvocationSpec) (*protos.Propo return &protos.Proposal{Header: hdrBytes, Payload: ccPropPayloadBytes}, nil } -// CreateProposalResponse create's the underlying payload objects in a TransactionAction -func CreateProposalResponse(typ protos.Header_Type, prop *protos.Proposal, ccEvents *protos.ChaincodeEvent, simulationResults []byte, endorsement *protos.Endorsement) (*protos.ProposalResponse, error) { - if typ != protos.Header_CHAINCODE { - panic("-----Only CHAINCODE Type is supported-----") +func GetBytesProposalResponsePayload(hash []byte, epoch []byte, result []byte, event []byte) ([]byte, error) { + cAct := &protos.ChaincodeAction{Events: event, Results: result} + cActBytes, err := proto.Marshal(cAct) + if err != nil { + return nil, err + } + + prp := &protos.ProposalResponsePayload{Epoch: epoch, Extension: cActBytes, ProposalHash: hash} + prpBytes, err := proto.Marshal(prp) + if err != nil { + return nil, err } - var err error - var cceventsBytes []byte + return prpBytes, nil +} - if ccEvents != nil { - cceventsBytes, err = proto.Marshal(ccEvents) - if err != nil { - return nil, err - } +func GetBytesChaincodeProposalPayload(cpp *protos.ChaincodeProposalPayload) ([]byte, error) { + cppBytes, err := proto.Marshal(cpp) + if err != nil { + return nil, err } - ext := &protos.ChaincodeAction{Results: simulationResults, Events: cceventsBytes} - extBytes, err := proto.Marshal(ext) + return cppBytes, nil +} + +func GetBytesChaincodeEvent(event *protos.ChaincodeEvent) ([]byte, error) { + eventBytes, err := proto.Marshal(event) if err != nil { return nil, err } - pRespPayload := &protos.ProposalResponsePayload{ProposalHash: []byte("TODO-use proposal to generate hash"), Epoch: []byte("TODO-compute epoch"), Extension: extBytes} + return eventBytes, nil +} - pRespPayloadBytes, err := proto.Marshal(pRespPayload) +func GetBytesProposalResponse(prpBytes []byte, endorsement *protos.Endorsement) ([]byte, error) { + resp := &protos.ProposalResponse{ + // Timestamp: TODO! + Version: 1, // TODO: pick right version number + Endorsement: endorsement, + Payload: prpBytes, + Response: &protos.Response2{Status: 200, Message: "OK"}} + respBytes, err := proto.Marshal(resp) if err != nil { return nil, err } - propResp := &protos.ProposalResponse{Payload: pRespPayloadBytes, Endorsement: endorsement} + return respBytes, nil +} + +func GetProposalHash(header []byte, ccPropPayl []byte, visibility []byte) ([]byte, error) { + // unmarshal the chaincode proposal payload + cpp := &protos.ChaincodeProposalPayload{} + err := proto.Unmarshal(ccPropPayl, cpp) + if err != nil { + return nil, errors.New("Failure while unmarshalling the ChaincodeProposalPayload!") + } + + // strip the transient bytes off the payload - this needs to be done no matter the visibility mode + cppNoTransient := &protos.ChaincodeProposalPayload{Input: cpp.Input, Transient: nil} + cppBytes, err := GetBytesChaincodeProposalPayload(cppNoTransient) + if err != nil { + return nil, errors.New("Failure while marshalling the ChaincodeProposalPayload!") + } + + // TODO: handle payload visibility - it needs to be defined first! + // here, as an example, I'll code the visibility policy that allows the + // full header but only the hash of the payload + + // TODO: use bccsp interfaces and providers as soon as they are ready! + hash1 := primitives.GetDefaultHash()() + hash1.Write(cppBytes) // hash the serialized ChaincodeProposalPayload object (stripped of the transient bytes) + hash2 := primitives.GetDefaultHash()() + hash2.Write(header) // hash the serialized Header object + hash2.Write(hash1.Sum(nil)) // hash the hash of the serialized ChaincodeProposalPayload object - return propResp, nil + return hash2.Sum(nil), nil } diff --git a/protos/utils/proputils_test.go b/protos/utils/proputils_test.go new file mode 100644 index 00000000000..a1873039633 --- /dev/null +++ b/protos/utils/proputils_test.go @@ -0,0 +1,159 @@ +/* +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 utils + +import ( + "bytes" + "testing" + + "github.com/hyperledger/fabric/protos" +) + +func createCIS() *protos.ChaincodeInvocationSpec { + return &protos.ChaincodeInvocationSpec{ + ChaincodeSpec: &protos.ChaincodeSpec{ + Type: protos.ChaincodeSpec_GOLANG, + ChaincodeID: &protos.ChaincodeID{Name: "chaincode_name"}, + CtorMsg: &protos.ChaincodeInput{Args: [][]byte{[]byte("arg1"), []byte("arg2")}}}} +} + +func TestProposal(t *testing.T) { + // create a proposal from a ChaincodeInvocationSpec + prop, err := CreateChaincodeProposal(createCIS(), []byte("creator")) + if err != nil { + t.Fatalf("Could not create chaincode proposal, err %s\n", err) + return + } + + // get back the header + hdr, err := GetHeader(prop) + if err != nil { + t.Fatalf("Could not extract the header from the proposal, err %s\n", err) + } + + // sanity check on header + if hdr.Type != protos.Header_CHAINCODE || + hdr.Nonce == nil || + string(hdr.Creator) != "creator" { + t.Fatalf("Invalid header after unmarshalling\n") + return + } + + // get back the header extension + hdrExt, err := GetChaincodeHeaderExtension(hdr) + if err != nil { + t.Fatalf("Could not extract the header extensions from the proposal, err %s\n", err) + return + } + + // sanity check on header extension + if string(hdrExt.ChaincodeID.Name) != "chaincode_name" { + t.Fatalf("Invalid header extension after unmarshalling\n") + return + } + + // get back the ChaincodeInvocationSpec + cis, err := GetChaincodeInvocationSpec(prop) + if err != nil { + t.Fatalf("Could not extract chaincode invocation spec from header, err %s\n", err) + return + } + + // sanity check on cis + if cis.ChaincodeSpec.Type != protos.ChaincodeSpec_GOLANG || + cis.ChaincodeSpec.ChaincodeID.Name != "chaincode_name" || + len(cis.ChaincodeSpec.CtorMsg.Args) != 2 || + string(cis.ChaincodeSpec.CtorMsg.Args[0]) != "arg1" || + string(cis.ChaincodeSpec.CtorMsg.Args[1]) != "arg2" { + t.Fatalf("Invalid chaincode invocation spec after unmarshalling\n") + return + } +} + +func TestProposalResponse(t *testing.T) { + events := &protos.ChaincodeEvent{ + ChaincodeID: "ccid", + EventName: "EventName", + Payload: []byte("EventPayload"), + TxID: "TxID"} + + pHashBytes := []byte("proposal_hash") + epoch := []byte("epoch") + results := []byte("results") + eventBytes, err := GetBytesChaincodeEvent(events) + if err != nil { + t.Fatalf("Failure while marshalling the ProposalResponsePayload") + return + } + + // get the bytes of the ProposalResponsePayload + prpBytes, err := GetBytesProposalResponsePayload(pHashBytes, epoch, results, eventBytes) + if err != nil { + t.Fatalf("Failure while marshalling the ProposalResponsePayload") + return + } + + // get the ProposalResponsePayload message + prp, err := GetProposalResponsePayload(prpBytes) + if err != nil { + t.Fatalf("Failure while unmarshalling the ProposalResponsePayload") + return + } + + // sanity check on prp + if string(prp.Epoch) != "epoch" || + string(prp.ProposalHash) != "proposal_hash" { + t.Fatalf("Invalid ProposalResponsePayload after unmarshalling") + return + } + + // get the ChaincodeAction message + act, err := GetChaincodeAction(prp.Extension) + if err != nil { + t.Fatalf("Failure while unmarshalling the ChaincodeAction") + return + } + + // sanity check on the action + if string(act.Results) != "results" { + t.Fatalf("Invalid actions after unmarshalling") + return + } + + // create a proposal response + prBytes, err := GetBytesProposalResponse(prpBytes, &protos.Endorsement{Endorser: []byte("endorser"), Signature: []byte("signature")}) + if err != nil { + t.Fatalf("Failure while marshalling the ProposalResponse") + return + } + + // get the proposal response message back + pr, err := GetProposalResponse(prBytes) + if err != nil { + t.Fatalf("Failure while unmarshalling the ProposalResponse") + return + } + + // sanity check on pr + if pr.Response.Status != 200 || + string(pr.Endorsement.Signature) != "signature" || + string(pr.Endorsement.Endorser) != "endorser" || + bytes.Compare(pr.Payload, prpBytes) != 0 { + t.Fatalf("Invalid ProposalResponse after unmarshalling") + return + } +}