Skip to content

Commit

Permalink
[FAB-1238] implement upgrade of lccc
Browse files Browse the repository at this point in the history
Add upgrade method for lccc. This method checkes DeploymentSpec
, replaces existing DeploymentSpec with new one, and updates chaincode
version with +1 simply. Finally return new version of chaincode.
https://jira.hyperledger.org/browse/FAB-1238

Change-Id: I5b314915c0ad966896669bfe6c95bdbe69e82aeb
Signed-off-by: jiangyaoguo <jiangyaoguo@gmail.com>
  • Loading branch information
jiangyaoguo committed Dec 8, 2016
1 parent 591d16d commit 09fe0da
Show file tree
Hide file tree
Showing 4 changed files with 214 additions and 10 deletions.
71 changes: 69 additions & 2 deletions core/chaincode/lccc.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down Expand Up @@ -63,6 +63,9 @@ const (
//DEPLOY deploy command
DEPLOY = "deploy"

//UPGRADE upgrade chaincode
UPGRADE = "upgrade"

//GETCCINFO get chaincode
GETCCINFO = "getid"

Expand All @@ -71,6 +74,9 @@ const (

//characters used in chaincodenamespace
specialChars = "/:[]${}"

// chaincode version when deploy
startVersion = 0
)

//---------- the LCCC -----------------
Expand Down Expand Up @@ -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

Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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))
Expand Down
68 changes: 68 additions & 0 deletions core/chaincode/lccc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
}
2 changes: 1 addition & 1 deletion core/endorser/endorser.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
83 changes: 76 additions & 7 deletions core/endorser/endorser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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

Expand All @@ -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
}
Expand All @@ -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)
}
Expand Down Expand Up @@ -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"))
Expand Down

0 comments on commit 09fe0da

Please sign in to comment.