diff --git a/core/chaincode/lccc.go b/core/chaincode/lccc.go index 1030d98e38e..1e4c46b908d 100644 --- a/core/chaincode/lccc.go +++ b/core/chaincode/lccc.go @@ -33,7 +33,7 @@ import ( //define the datastructure for chaincodes to be serialized by proto type chaincodeData struct { name string `protobuf:"bytes,1,opt,name=name"` - version int32 `protobuf:"bytes,2,opt,name=version,proto3"` + version int32 `protobuf:"varint,2,opt,name=version,proto3"` depSpec []byte `protobuf:"bytes,3,opt,name=depSpec,proto3"` escc string `protobuf:"bytes,4,opt,name=escc"` vscc string `protobuf:"bytes,5,opt,name=vscc"` @@ -63,6 +63,9 @@ const ( //DEPLOY deploy command DEPLOY = "deploy" + //UPGRADE upgrade chaincode + UPGRADE = "upgrade" + //GETCCINFO get chaincode GETCCINFO = "getid" @@ -71,6 +74,9 @@ const ( //characters used in chaincodenamespace specialChars = "/:[]${}" + + // chaincode version when deploy + startVersion = 0 ) //---------- the LCCC ----------------- @@ -137,6 +143,12 @@ func (t ExistsErr) Error() string { return fmt.Sprintf("Chaincode exists %s", string(t)) } +type ChaincodeNotFoundErr string + +func (t ChaincodeNotFoundErr) Error() string { + return fmt.Sprintf("chaincode not found %s", string(t)) +} + //InvalidChainNameErr invalid chain name error type InvalidChainNameErr string @@ -161,7 +173,17 @@ func (m MarshallErr) Error() string { //-------------- helper functions ------------------ //create the chaincode on the given chain func (lccc *LifeCycleSysCC) createChaincode(stub shim.ChaincodeStubInterface, chainname string, ccname string, cccode []byte) (*chaincodeData, error) { - cd := &chaincodeData{name: ccname, depSpec: cccode} + return lccc.putChaincodeData(stub, chainname, ccname, startVersion, cccode) +} + +//upgrade the chaincode on the given chain +func (lccc *LifeCycleSysCC) upgradeChaincode(stub shim.ChaincodeStubInterface, chainname string, ccname string, version int32, cccode []byte) (*chaincodeData, error) { + return lccc.putChaincodeData(stub, chainname, ccname, version, cccode) +} + +//create the chaincode on the given chain +func (lccc *LifeCycleSysCC) putChaincodeData(stub shim.ChaincodeStubInterface, chainname string, ccname string, version int32, cccode []byte) (*chaincodeData, error) { + cd := &chaincodeData{name: ccname, version: version, depSpec: cccode} cdbytes, err := proto.Marshal(cd) if err != nil { return nil, err @@ -319,6 +341,39 @@ func (lccc *LifeCycleSysCC) executeDeploy(stub shim.ChaincodeStubInterface, chai return err } +//this implements "upgrade" Invoke transaction +func (lccc *LifeCycleSysCC) executeUpgrade(stub shim.ChaincodeStubInterface, chainName string, code []byte) ([]byte, error) { + cds, err := lccc.getChaincodeDeploymentSpec(code) + if err != nil { + return nil, err + } + + chaincodeName := cds.ChaincodeSpec.ChaincodeID.Name + if !lccc.isValidChaincodeName(chaincodeName) { + return nil, InvalidChaincodeNameErr(chaincodeName) + } + + // check for existence of chaincode + cd, err := lccc.getChaincode(stub, chainName, chaincodeName) + if cd == nil { + return nil, ChaincodeNotFoundErr(chainName) + } + + if err = lccc.acl(stub, chainName, cds); err != nil { + return nil, err + } + + // replace the ChaincodeDeploymentSpec + newVersion := cd.version + 1 + newCD, err := lccc.upgradeChaincode(stub, chainName, chaincodeName, newVersion, code) + if err != nil { + return nil, err + } + + strVer := fmt.Sprint(newCD.version) + return []byte(strVer), nil +} + //-------------- the chaincode stub interface implementation ---------- //Init does nothing @@ -359,6 +414,18 @@ func (lccc *LifeCycleSysCC) Invoke(stub shim.ChaincodeStubInterface) ([]byte, er err := lccc.executeDeploy(stub, chainname, code) return nil, err + case UPGRADE: + if len(args) != 3 { + return nil, InvalidArgsLenErr(len(args)) + } + + chainname := string(args[1]) + if !lccc.isValidChainName(chainname) { + return nil, InvalidChainNameErr(chainname) + } + + code := args[2] + return lccc.executeUpgrade(stub, chainname, code) case GETCCINFO, GETDEPSPEC: if len(args) != 3 { return nil, InvalidArgsLenErr(len(args)) diff --git a/core/chaincode/lccc_test.go b/core/chaincode/lccc_test.go index 08e6409aab5..3d44c82c5ab 100644 --- a/core/chaincode/lccc_test.go +++ b/core/chaincode/lccc_test.go @@ -248,3 +248,71 @@ func TestRetryFailedDeploy(t *testing.T) { t.FailNow() } } + +//TestUpgrade tests the upgrade function +func TestUpgrade(t *testing.T) { + initialize() + + scc := new(LifeCycleSysCC) + stub := shim.NewMockStub("lccc", scc) + + cds, err := constructDeploymentSpec("example02", "github.com/hyperledger/fabric/examples/chaincode/go/chaincode_example02", [][]byte{[]byte("init"), []byte("a"), []byte("100"), []byte("b"), []byte("200")}) + var b []byte + if b, err = proto.Marshal(cds); err != nil || b == nil { + t.Fatalf("Marshal DeploymentSpec failed") + } + + args := [][]byte{[]byte(DEPLOY), []byte("test"), b} + if _, err := stub.MockInvoke("1", args); err != nil { + t.Fatalf("Deploy chaincode error: %v", err) + } + + newCds, err := constructDeploymentSpec("example02", "github.com/hyperledger/fabric/examples/chaincode/go/chaincode_example02", [][]byte{[]byte("init"), []byte("a"), []byte("100"), []byte("b"), []byte("200")}) + var newb []byte + if newb, err = proto.Marshal(newCds); err != nil || newb == nil { + t.Fatalf("Marshal DeploymentSpec failed") + } + + args = [][]byte{[]byte(UPGRADE), []byte("test"), newb} + version, err := stub.MockInvoke("1", args) + if err != nil { + t.Fatalf("Upgrade chaincode error: %v", err) + } + + expectVer := "1" + newVer := string(version) + if newVer != expectVer { + t.Fatalf("Upgrade chaincode version error, expected %s, got %s", expectVer, newVer) + } +} + +//TestUpgradeNonExistChaincode tests upgrade non exist chaincode +func TestUpgradeNonExistChaincode(t *testing.T) { + initialize() + + scc := new(LifeCycleSysCC) + stub := shim.NewMockStub("lccc", scc) + + cds, err := constructDeploymentSpec("example02", "github.com/hyperledger/fabric/examples/chaincode/go/chaincode_example02", [][]byte{[]byte("init"), []byte("a"), []byte("100"), []byte("b"), []byte("200")}) + var b []byte + if b, err = proto.Marshal(cds); err != nil || b == nil { + t.Fatalf("Marshal DeploymentSpec failed") + } + + args := [][]byte{[]byte(DEPLOY), []byte("test"), b} + if _, err := stub.MockInvoke("1", args); err != nil { + t.Fatalf("Deploy chaincode error: %v", err) + } + + newCds, err := constructDeploymentSpec("example03", "github.com/hyperledger/fabric/examples/chaincode/go/chaincode_example02", [][]byte{[]byte("init"), []byte("a"), []byte("100"), []byte("b"), []byte("200")}) + var newb []byte + if newb, err = proto.Marshal(newCds); err != nil || newb == nil { + t.Fatalf("Marshal DeploymentSpec failed") + } + + args = [][]byte{[]byte(UPGRADE), []byte("test"), newb} + _, err = stub.MockInvoke("1", args) + if _, ok := err.(ChaincodeNotFoundErr); !ok { + t.FailNow() + } +} diff --git a/core/endorser/endorser.go b/core/endorser/endorser.go index 1ef58989f97..a7390dd46c7 100644 --- a/core/endorser/endorser.go +++ b/core/endorser/endorser.go @@ -105,7 +105,7 @@ func (e *Endorser) callChaincode(ctxt context.Context, chainID string, txid stri // //NOTE that if there's an error all simulation, including the chaincode //table changes in lccc will be thrown away - if cid.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" || string(cis.ChaincodeSpec.CtorMsg.Args[0]) == "upgrade") { var cds *pb.ChaincodeDeploymentSpec cds, err = putils.GetChaincodeDeploymentSpec(cis.ChaincodeSpec.CtorMsg.Args[2]) if err != nil { diff --git a/core/endorser/endorser_test.go b/core/endorser/endorser_test.go index 7f9b873b9a0..c4f9b21c2c7 100644 --- a/core/endorser/endorser_test.go +++ b/core/endorser/endorser_test.go @@ -119,24 +119,38 @@ 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, chainID string, creator []byte) (*pb.Proposal, error) { +func getInvokeProposal(cis *pb.ChaincodeInvocationSpec, chainID string, creator []byte) (*pb.Proposal, error) { uuid := util.GenerateUUID() return pbutils.CreateChaincodeProposal(uuid, chainID, cis, creator) } -//getDeployProposal gets the proposal for the chaincode deployment -//the payload is a ChaincodeDeploymentSpec func getDeployProposal(cds *pb.ChaincodeDeploymentSpec, chainID string, creator []byte) (*pb.Proposal, error) { + return getDeployOrUpgradeProposal(cds, chainID, creator, false) +} + +func getUpgradeProposal(cds *pb.ChaincodeDeploymentSpec, chainID string, creator []byte) (*pb.Proposal, error) { + return getDeployOrUpgradeProposal(cds, chainID, creator, true) +} + +//getDeployOrUpgradeProposal gets the proposal for the chaincode deploy or upgrade +//the payload is a ChaincodeDeploymentSpec +func getDeployOrUpgradeProposal(cds *pb.ChaincodeDeploymentSpec, chainID string, creator []byte, upgrade bool) (*pb.Proposal, error) { b, err := proto.Marshal(cds) if err != nil { return nil, err } + var propType string + if upgrade { + propType = "upgrade" + } else { + propType = "deploy" + } //wrap the deployment in an invocation spec to lccc... - lcccSpec := &pb.ChaincodeInvocationSpec{ChaincodeSpec: &pb.ChaincodeSpec{Type: pb.ChaincodeSpec_GOLANG, ChaincodeID: &pb.ChaincodeID{Name: "lccc"}, CtorMsg: &pb.ChaincodeInput{Args: [][]byte{[]byte("deploy"), []byte(chainID), b}}}} + lcccSpec := &pb.ChaincodeInvocationSpec{ChaincodeSpec: &pb.ChaincodeSpec{Type: pb.ChaincodeSpec_GOLANG, ChaincodeID: &pb.ChaincodeID{Name: "lccc"}, CtorMsg: &pb.ChaincodeInput{Args: [][]byte{[]byte(propType), []byte(chainID), b}}}} //...and get the proposal for it - return getProposal(lcccSpec, chainID, creator) + return getInvokeProposal(lcccSpec, chainID, creator) } func getSignedProposal(prop *pb.Proposal, signer msp.SigningIdentity) (*pb.SignedProposal, error) { @@ -163,6 +177,14 @@ func getDeploymentSpec(context context.Context, spec *pb.ChaincodeSpec) (*pb.Cha } func deploy(endorserServer pb.EndorserServer, chainID string, spec *pb.ChaincodeSpec, f func(*pb.ChaincodeDeploymentSpec)) (*pb.ProposalResponse, *pb.Proposal, error) { + return deployOrUpgrade(endorserServer, chainID, spec, f, false) +} + +func upgrade(endorserServer pb.EndorserServer, chainID string, spec *pb.ChaincodeSpec, f func(*pb.ChaincodeDeploymentSpec)) (*pb.ProposalResponse, *pb.Proposal, error) { + return deployOrUpgrade(endorserServer, chainID, spec, f, true) +} + +func deployOrUpgrade(endorserServer pb.EndorserServer, chainID string, spec *pb.ChaincodeSpec, f func(*pb.ChaincodeDeploymentSpec), upgrade bool) (*pb.ProposalResponse, *pb.Proposal, error) { var err error var depSpec *pb.ChaincodeDeploymentSpec @@ -182,7 +204,11 @@ func deploy(endorserServer pb.EndorserServer, chainID string, spec *pb.Chaincode } var prop *pb.Proposal - prop, err = getDeployProposal(depSpec, chainID, creator) + if upgrade { + prop, err = getUpgradeProposal(depSpec, chainID, creator) + } else { + prop, err = getDeployProposal(depSpec, chainID, creator) + } if err != nil { return nil, nil, err } @@ -208,7 +234,7 @@ func invoke(chainID string, spec *pb.ChaincodeSpec) (*pb.ProposalResponse, error } var prop *pb.Proposal - prop, err = getProposal(invocation, chainID, creator) + prop, err = getInvokeProposal(invocation, chainID, creator) if err != nil { return nil, fmt.Errorf("Error creating proposal %s: %s\n", spec.ChaincodeID, err) } @@ -352,6 +378,49 @@ func TestDeployAndInvoke(t *testing.T) { chaincode.GetChain().Stop(ctxt, chainID, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: &pb.ChaincodeSpec{ChaincodeID: chaincodeID}}) } +// TestUpgradeAndInvoke deploys chaincode_example01, upgrade it with chaincode_example02, then invoke it +func TestDeployAndUpgrade(t *testing.T) { + chainID := util.GetTestChainID() + var ctxt = context.Background() + + url1 := "github.com/hyperledger/fabric/examples/chaincode/go/chaincode_example01" + url2 := "github.com/hyperledger/fabric/examples/chaincode/go/chaincode_example02" + chaincodeID1 := &pb.ChaincodeID{Path: url1, Name: "upgradeex01"} + chaincodeID2 := &pb.ChaincodeID{Path: url2, Name: "upgradeex01"} + + f := "init" + argsDeploy := util.ToChaincodeArgs(f, "a", "100", "b", "200") + spec := &pb.ChaincodeSpec{Type: 1, ChaincodeID: chaincodeID1, CtorMsg: &pb.ChaincodeInput{Args: argsDeploy}} + resp, prop, err := deploy(endorserServer, chainID, spec, nil) + chaincodeName := spec.ChaincodeID.Name + if err != nil { + t.Fail() + t.Logf("Error deploying <%s>: %s", chaincodeName, err) + return + } + + err = endorserServer.(*Endorser).commitTxSimulation(prop, chainID, signer, resp) + if err != nil { + t.Fail() + t.Logf("Error committing <%s>: %s", chaincodeName, err) + return + } + + argsUpgrade := util.ToChaincodeArgs(f, "a", "150", "b", "300") + spec = &pb.ChaincodeSpec{Type: 1, ChaincodeID: chaincodeID2, CtorMsg: &pb.ChaincodeInput{Args: argsUpgrade}} + resp, prop, err = upgrade(endorserServer, chainID, spec, nil) + if err != nil { + t.Fail() + t.Logf("Error upgrading <%s>: %s", chaincodeName, err) + return + } + + fmt.Printf("Upgrade test passed\n") + t.Logf("Upgrade test passed") + + chaincode.GetChain().Stop(ctxt, chainID, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: &pb.ChaincodeSpec{ChaincodeID: chaincodeID2}}) +} + func TestMain(m *testing.M) { SetupTestConfig() viper.Set("peer.fileSystemPath", filepath.Join(os.TempDir(), "hyperledger", "production"))