Commit 0a03aad9 authored by Christopher Ferris's avatar Christopher Ferris Committed by Gerrit Code Review
Browse files

Merge "chaincode life-cycle system chaincode for a chain" into feature/convergence

parents 5d76c5b0 a3687a1f
......@@ -21,7 +21,7 @@ Feature: Endorser
Scenario Outline: Basic deploy endorsement for chaincode through GRPC to multiple endorsers
Given we compose "<ComposeFile>"
And I wait "1" seconds
And I wait "5" seconds
And I register with CA supplying username "binhn" and secret "7avZQLwcUe9q" on peers:
| vp0 |
......@@ -29,7 +29,7 @@ Feature: Endorser
| funcName | arg1 | arg2 | arg3 | arg4 |
| init | a | 100 | b | 200 |
And user "binhn" creates a deployment proposal "proposal1" using chaincode spec "cc_spec"
And user "binhn" sends proposal "proposal1" to endorsers with timeout of "2" seconds:
And user "binhn" sends proposal "proposal1" to endorsers with timeout of "20" seconds:
| vp0 | vp1 | vp2 | vp3 |
And user "binhn" stores their last result as "proposal1Responses"
Then user "binhn" expects proposal responses "proposal1Responses" with status "200" from endorsers:
......@@ -54,7 +54,7 @@ Feature: Endorser
| init | a | 100 | b | 200 |
And user "binhn" sets ESCC to "my_escc" for chaincode spec "cc_spec"
And user "binhn" creates a deployment proposal "proposal1" using chaincode spec "cc_spec"
And user "binhn" sends proposal "proposal1" to endorsers with timeout of "2" seconds:
And user "binhn" sends proposal "proposal1" to endorsers with timeout of "20" seconds:
| vp0 | vp1 | vp2 | vp3 |
And user "binhn" stores their last result as "proposal1Responses"
Then user "binhn" expects proposal responses "proposal1Responses" with status "200" from endorsers:
......@@ -77,7 +77,7 @@ Feature: Endorser
| init | a | 100 | b | 200 |
And user "binhn" sets VSCC to "my_vscc" for chaincode spec "cc_spec"
And user "binhn" creates a deployment proposal "proposal1" using chaincode spec "cc_spec"
And user "binhn" sends proposal "proposal1" to endorsers with timeout of "2" seconds:
And user "binhn" sends proposal "proposal1" to endorsers with timeout of "20" seconds:
| vp0 | vp1 | vp2 | vp3 |
And user "binhn" stores their last result as "proposal1Responses"
Then user "binhn" expects proposal responses "proposal1Responses" with status "200" from endorsers:
......
......@@ -45,9 +45,6 @@ import (
// Logger for the shim package.
var chaincodeLogger = logging.MustGetLogger("shim")
// Handler to shim that handles all control logic.
var handler *Handler
// ChaincodeStub is an object passed to chaincode for shim side handling of
// APIs.
type ChaincodeStub struct {
......@@ -55,6 +52,7 @@ type ChaincodeStub struct {
securityContext *pb.ChaincodeSecurityContext
chaincodeEvent *pb.ChaincodeEvent
args [][]byte
handler *Handler
}
// Peer address derived from command line or env var
......@@ -150,7 +148,7 @@ func newPeerClientConnection() (*grpc.ClientConn, error) {
func chatWithPeer(chaincodename string, stream PeerChaincodeStream, cc Chaincode) error {
// Create the shim handler responsible for all control logic
handler = newChaincodeHandler(stream, cc)
handler := newChaincodeHandler(stream, cc)
defer stream.CloseSend()
// Send the ChaincodeID during register.
......@@ -232,7 +230,7 @@ func chatWithPeer(chaincodename string, stream PeerChaincodeStream, cc Chaincode
// -- init stub ---
// ChaincodeInvocation functionality
func (stub *ChaincodeStub) init(uuid string, secContext *pb.ChaincodeSecurityContext) {
func (stub *ChaincodeStub) init(handler *Handler, uuid string, secContext *pb.ChaincodeSecurityContext) {
stub.UUID = uuid
stub.securityContext = secContext
stub.args = [][]byte{}
......@@ -243,6 +241,7 @@ func (stub *ChaincodeStub) init(uuid string, secContext *pb.ChaincodeSecurityCon
} else {
panic("Arguments cannot be unmarshalled.")
}
stub.handler = handler
}
func InitTestStub(funargs ...string) *ChaincodeStub {
......@@ -250,7 +249,7 @@ func InitTestStub(funargs ...string) *ChaincodeStub {
allargs := util.ToChaincodeArgs(funargs...)
newCI := pb.ChaincodeInput{Args: allargs}
pl, _ := proto.Marshal(&newCI)
stub.init("TEST-uuid", &pb.ChaincodeSecurityContext{Payload: pl})
stub.init(&Handler{}, "TEST-uuid", &pb.ChaincodeSecurityContext{Payload: pl})
return &stub
}
......@@ -263,31 +262,31 @@ func InitTestStub(funargs ...string) *ChaincodeStub {
// same transaction context; that is, chaincode calling chaincode doesn't
// create a new transaction message.
func (stub *ChaincodeStub) InvokeChaincode(chaincodeName string, args [][]byte) ([]byte, error) {
return handler.handleInvokeChaincode(chaincodeName, args, stub.UUID)
return stub.handler.handleInvokeChaincode(chaincodeName, args, stub.UUID)
}
// QueryChaincode locally calls the specified chaincode `Query` using the
// same transaction context; that is, chaincode calling chaincode doesn't
// create a new transaction message.
func (stub *ChaincodeStub) QueryChaincode(chaincodeName string, args [][]byte) ([]byte, error) {
return handler.handleQueryChaincode(chaincodeName, args, stub.UUID)
return stub.handler.handleQueryChaincode(chaincodeName, args, stub.UUID)
}
// --------- State functions ----------
// GetState returns the byte array value specified by the `key`.
func (stub *ChaincodeStub) GetState(key string) ([]byte, error) {
return handler.handleGetState(key, stub.UUID)
return stub.handler.handleGetState(key, stub.UUID)
}
// PutState writes the specified `value` and `key` into the ledger.
func (stub *ChaincodeStub) PutState(key string, value []byte) error {
return handler.handlePutState(key, value, stub.UUID)
return stub.handler.handlePutState(key, value, stub.UUID)
}
// DelState removes the specified `key` and its value from the ledger.
func (stub *ChaincodeStub) DelState(key string) error {
return handler.handleDelState(key, stub.UUID)
return stub.handler.handleDelState(key, stub.UUID)
}
//ReadCertAttribute is used to read an specific attribute from the transaction certificate, *attributeName* is passed as input parameter to this function.
......@@ -338,11 +337,11 @@ type StateRangeQueryIterator struct {
// between the startKey and endKey, inclusive. The order in which keys are
// returned by the iterator is random.
func (stub *ChaincodeStub) RangeQueryState(startKey, endKey string) (StateRangeQueryIteratorInterface, error) {
response, err := handler.handleRangeQueryState(startKey, endKey, stub.UUID)
response, err := stub.handler.handleRangeQueryState(startKey, endKey, stub.UUID)
if err != nil {
return nil, err
}
return &StateRangeQueryIterator{handler, stub.UUID, response, 0}, nil
return &StateRangeQueryIterator{stub.handler, stub.UUID, response, 0}, nil
}
// HasNext returns true if the range query iterator contains additional keys
......
......@@ -222,7 +222,7 @@ func (handler *Handler) handleInit(msg *pb.ChaincodeMessage) {
// Call chaincode's Run
// Create the ChaincodeStub which the chaincode can use to callback
stub := new(ChaincodeStub)
stub.init(msg.Txid, msg.SecurityContext)
stub.init(handler, msg.Txid, msg.SecurityContext)
res, err := handler.cc.Init(stub)
// delete isTransaction entry
......@@ -289,7 +289,7 @@ func (handler *Handler) handleTransaction(msg *pb.ChaincodeMessage) {
// Call chaincode's Run
// Create the ChaincodeStub which the chaincode can use to callback
stub := new(ChaincodeStub)
stub.init(msg.Txid, msg.SecurityContext)
stub.init(handler, msg.Txid, msg.SecurityContext)
res, err := handler.cc.Invoke(stub)
// delete isTransaction entry
......@@ -336,7 +336,7 @@ func (handler *Handler) handleQuery(msg *pb.ChaincodeMessage) {
// Call chaincode's Query
// Create the ChaincodeStub which the chaincode can use to callback
stub := new(ChaincodeStub)
stub.init(msg.Txid, msg.SecurityContext)
stub.init(handler, msg.Txid, msg.SecurityContext)
res, err := handler.cc.Query(stub)
// delete isTransaction entry
......
......@@ -20,6 +20,7 @@ import (
"github.com/hyperledger/fabric/core/system_chaincode/api"
//import system chain codes here
"github.com/hyperledger/fabric/bddtests/syschaincode/noop"
"github.com/hyperledger/fabric/core/system_chaincode/lccc"
)
//see systemchaincode_test.go for an example using "sample_syscc"
......@@ -30,6 +31,13 @@ var systemChaincodes = []*api.SystemChaincode{
Path: "github.com/hyperledger/fabric/bddtests/syschaincode/noop",
InitArgs: [][]byte{},
Chaincode: &noop.SystemChaincode{},
},
{
Enabled: true,
Name: "lccc",
Path: "github.com/hyperledger/fabric/core/system_chaincode/lccc",
InitArgs: [][]byte{[]byte("")},
Chaincode: &lccc.LifeCycleSysCC{},
}}
//RegisterSysCCs is the hook for system chaincodes where system chaincodes are registered with the fabric
......
/*
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 lccc
import (
"fmt"
"github.com/golang/protobuf/proto"
"github.com/hyperledger/fabric/core/chaincode"
"github.com/hyperledger/fabric/core/chaincode/shim"
pb "github.com/hyperledger/fabric/protos"
"github.com/op/go-logging"
"golang.org/x/net/context"
)
//The life cycle system chaincode manages chaincodes deployed
//on this peer. It manages chaincodes via Invoke proposals.
// "Args":["deploy",<ChaincodeDeploymentSpec>]
// "Args":["upgrade",<ChaincodeDeploymentSpec>]
// "Args":["stop",<ChaincodeInvocationSpec>]
// "Args":["start",<ChaincodeInvocationSpec>]
var logger = logging.MustGetLogger("lccc")
const (
//CHAINCODETABLE prefix for chaincode tables
CHAINCODETABLE = "chaincodes"
//chaincode lifecyle commands
//DEPLOY deploy command
DEPLOY = "deploy"
//chaincode query commands
//GETCCINFO get chaincode
GETCCINFO = "getid"
)
//---------- the LCCC -----------------
// LifeCycleSysCC implements chaincode lifecycle and policies aroud it
type LifeCycleSysCC struct {
}
//----------------errors---------------
//AlreadyRegisteredErr Already registered error
type AlreadyRegisteredErr string
func (f AlreadyRegisteredErr) Error() string {
return fmt.Sprintf("%s already registered", string(f))
}
//InvalidFunctionErr invalid function error
type InvalidFunctionErr string
func (f InvalidFunctionErr) Error() string {
return fmt.Sprintf("invalid function to lccc %s", string(f))
}
//InvalidArgsLenErr invalid arguments length error
type InvalidArgsLenErr int
func (i InvalidArgsLenErr) Error() string {
return fmt.Sprintf("invalid number of argument to lccc %d", int(i))
}
//InvalidArgsErr invalid arguments error
type InvalidArgsErr int
func (i InvalidArgsErr) Error() string {
return fmt.Sprintf("invalid argument (%d) to lccc", int(i))
}
//TXExistsErr transaction exists error
type TXExistsErr string
func (t TXExistsErr) Error() string {
return fmt.Sprintf("transaction exists %s", string(t))
}
//TXNotFoundErr transaction not found error
type TXNotFoundErr string
func (t TXNotFoundErr) Error() string {
return fmt.Sprintf("transaction not found %s", string(t))
}
//InvalidDeploymentSpecErr invalide chaincode deployment spec error
type InvalidDeploymentSpecErr string
func (f InvalidDeploymentSpecErr) Error() string {
return fmt.Sprintf("Invalid deployment spec : %s", string(f))
}
//ChaincodeExistsErr chaincode exists error
type ChaincodeExistsErr string
func (t ChaincodeExistsErr) Error() string {
return fmt.Sprintf("Chaincode exists %s", string(t))
}
//InvalidChainNameErr invalid function error
type InvalidChainNameErr string
func (f InvalidChainNameErr) Error() string {
return fmt.Sprintf("invalid chain name %s", string(f))
}
//-------------- helper functions ------------------
//create the table to maintain list of chaincodes maintained in this
//blockchain.
func (lccc *LifeCycleSysCC) createChaincodeTable(stub shim.ChaincodeStubInterface, cctable string) error {
// Create table one
var colDefs []*shim.ColumnDefinition
nameColDef := shim.ColumnDefinition{Name: "name",
Type: shim.ColumnDefinition_STRING, Key: true}
versColDef := shim.ColumnDefinition{Name: "version",
Type: shim.ColumnDefinition_INT32, Key: false}
//QUESTION - Should code be separately maintained ?
codeDef := shim.ColumnDefinition{Name: "code",
Type: shim.ColumnDefinition_BYTES, Key: false}
colDefs = append(colDefs, &nameColDef)
colDefs = append(colDefs, &versColDef)
colDefs = append(colDefs, &codeDef)
return stub.CreateTable(cctable, colDefs)
}
//register create the chaincode table. name can be used to different
//tables of chaincodes. This would provide the way to associate chaincodes
//with chains(and ledgers)
func (lccc *LifeCycleSysCC) register(stub shim.ChaincodeStubInterface, name string) error {
ccname := CHAINCODETABLE + "-" + name
row, err := stub.GetTable(ccname)
if err == nil && row != nil { //table exists, do nothing
return AlreadyRegisteredErr(name)
}
//there may be other err's but assume "not exists". Anything
//more serious than that bound to show up
err = lccc.createChaincodeTable(stub, ccname)
return err
}
//create the chaincode on the given chain
func (lccc *LifeCycleSysCC) createChaincode(stub shim.ChaincodeStubInterface, chainname string, ccname string, cccode []byte) (*shim.Row, error) {
var columns []*shim.Column
nameCol := shim.Column{Value: &shim.Column_String_{String_: ccname}}
versCol := shim.Column{Value: &shim.Column_Int32{Int32: 0}}
codeCol := shim.Column{Value: &shim.Column_Bytes{Bytes: cccode}}
columns = append(columns, &nameCol)
columns = append(columns, &versCol)
columns = append(columns, &codeCol)
row := &shim.Row{Columns: columns}
_, err := stub.InsertRow(CHAINCODETABLE+"-"+chainname, *row)
if err != nil {
return nil, fmt.Errorf("insertion of chaincode failed. %s", err)
}
return row, nil
}
//checks for existence of chaincode on the given chain
func (lccc *LifeCycleSysCC) getChaincode(stub shim.ChaincodeStubInterface, chainname string, ccname string) (shim.Row, bool, error) {
var columns []shim.Column
nameCol := shim.Column{Value: &shim.Column_String_{String_: ccname}}
columns = append(columns, nameCol)
row, err := stub.GetRow(CHAINCODETABLE+"-"+chainname, columns)
if err != nil {
return shim.Row{}, false, err
}
if len(row.Columns) > 0 {
return row, true, nil
}
return row, false, nil
}
//getChaincodeDeploymentSpec returns a ChaincodeDeploymentSpec given args
func (lccc *LifeCycleSysCC) getChaincodeDeploymentSpec(code []byte) (*pb.ChaincodeDeploymentSpec, error) {
cds := &pb.ChaincodeDeploymentSpec{}
err := proto.Unmarshal(code, cds)
if err != nil {
return nil, InvalidDeploymentSpecErr(err.Error())
}
return cds, nil
}
//do access control
func (lccc *LifeCycleSysCC) acl(stub shim.ChaincodeStubInterface, chainname chaincode.ChainName, cds *pb.ChaincodeDeploymentSpec) error {
return nil
}
//check validity of chain name
func (lccc *LifeCycleSysCC) isValidChainName(chainname string) bool {
//TODO we probably need more checks and have
if chainname == "" {
return false
}
return true
}
//deploy the chaincode on to the chain
func (lccc *LifeCycleSysCC) deploy(stub shim.ChaincodeStubInterface, chainname string, cds *pb.ChaincodeDeploymentSpec) error {
_, exists, err := lccc.getChaincode(stub, chainname, cds.ChaincodeSpec.ChaincodeID.Name)
if exists {
return ChaincodeExistsErr(cds.ChaincodeSpec.ChaincodeID.Name)
}
//TODO : this needs to be converted to another data structure to be handled
// by the chaincode framework (which currently handles "Transaction")
t, err := lccc.toTransaction(cds)
if err != nil {
return fmt.Errorf("could not convert proposal to transaction %s", err)
}
//if unit testing, just return..we cannot do the actual deploy
if _, ismock := stub.(*shim.MockStub); ismock {
//we got this far just stop short of actual deploy for test purposes
return nil
}
ctxt := context.Background()
//TODO - create chaincode support for chainname, for now use DefaultChain
//chaincodeSupport := chaincode.GetChain(chaincode.ChainName(chainname))
chaincodeSupport := chaincode.GetChain(chaincode.DefaultChain)
_, err = chaincodeSupport.Deploy(ctxt, t)
if err != nil {
return fmt.Errorf("Failed to deploy chaincode spec(%s)", err)
}
//launch and wait for ready
_, _, err = chaincodeSupport.Launch(ctxt, t)
if err != nil {
return fmt.Errorf("%s", err)
}
/************ STOP WHEN WE ARE ON NEWLEDGER
//stop now that we are done
chaincodeSupport.Stop(ctxt, cds)
**********/
return nil
}
//this implements "deploy" Invoke transaction
func (lccc *LifeCycleSysCC) executeDeploy(stub shim.ChaincodeStubInterface, chainname string, code []byte) error {
//lazy creation of chaincode table for chainname...its possible
//there are chains without chaincodes
if err := lccc.register(stub, chainname); err != nil {
//if its already registered, ok... proceed
if _, ok := err.(AlreadyRegisteredErr); !ok {
return err
}
}
cds, err := lccc.getChaincodeDeploymentSpec(code)
if err != nil {
return err
}
if err = lccc.acl(stub, chaincode.DefaultChain, cds); err != nil {
return err
}
if err = lccc.deploy(stub, chainname, cds); err != nil {
return err
}
_, err = lccc.createChaincode(stub, chainname, cds.ChaincodeSpec.ChaincodeID.Name, cds.CodePackage)
return err
}
//TODO - this is temporary till we use Transaction in chaincode code
func (lccc *LifeCycleSysCC) toTransaction(cds *pb.ChaincodeDeploymentSpec) (*pb.Transaction, error) {
return pb.NewChaincodeDeployTransaction(cds, cds.ChaincodeSpec.ChaincodeID.Name)
}
//-------------- the chaincode stub interface implementation ----------
//Init does nothing
func (lccc *LifeCycleSysCC) Init(stub shim.ChaincodeStubInterface) ([]byte, error) {
return nil, nil
}
// Invoke implements lifecycle functions "deploy", "start", "stop", "upgrade".
// Deploy's arguments - {[]byte("deploy"), []byte(<chainname>), <unmarshalled pb.ChaincodeDeploymentSpec>}
//
// Invoke also implements some query-like functions
// Get chaincode arguments - {[]byte("getid"), []byte(<chainname>), []byte(<chaincodename>)}
func (lccc *LifeCycleSysCC) Invoke(stub shim.ChaincodeStubInterface) ([]byte, error) {
args := stub.GetArgs()
if len(args) < 1 {
return nil, InvalidArgsLenErr(len(args))
}
function := string(args[0])
switch function {
case DEPLOY:
if len(args) != 3 {
return nil, InvalidArgsLenErr(len(args))
}
//chain the chaincode shoud be associated with. It
//should be created with a register call
chainname := string(args[1])
if !lccc.isValidChainName(chainname) {
return nil, InvalidChainNameErr(chainname)
}
//bytes corresponding to deployment spec
code := args[2]
err := lccc.executeDeploy(stub, chainname, code)
return nil, err
case GETCCINFO:
if len(args) != 3 {
return nil, InvalidArgsLenErr(len(args))
}
chain := string(args[1])
ccname := string(args[2])
//get chaincode given <chain, name>
ccrow, exists, _ := lccc.getChaincode(stub, chain, ccname)
if !exists {
logger.Debug("ChaincodeID [%s/%s] does not exist", chain, ccname)
return nil, TXNotFoundErr(chain + "/" + ccname)
}
return []byte(ccrow.Columns[1].GetString_()), nil
}
return nil, InvalidFunctionErr(function)
}
// Query is no longer implemented. Will be removed
func (lccc *LifeCycleSysCC) Query(stub shim.ChaincodeStubInterface) ([]byte, error) {
return nil, nil
}
/*
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 lccc
import (
"testing"
"time"
"github.com/golang/protobuf/proto"
"github.com/hyperledger/fabric/core/chaincode"
"github.com/hyperledger/fabric/core/chaincode/shim"
"github.com/hyperledger/fabric/core/container"
pb "github.com/hyperledger/fabric/protos"
"google.golang.org/grpc"
)
func register(stub *shim.MockStub, ccname string) error {
args := [][]byte{[]byte("register"), []byte(ccname)}
if _, err := stub.MockInvoke("1", args); err != nil {
return err
}
return nil
}
func constructDeploymentSpec(path string, initArgs [][]byte) (*pb.ChaincodeDeploymentSpec, error) {
spec := &pb.ChaincodeSpec{Type: 1, ChaincodeID: &pb.ChaincodeID{Path: path}, CtorMsg: &pb.ChaincodeInput{Args: initArgs}}
codePackageBytes, err := container.GetChaincodePackageBytes(spec)
if err != nil {
return nil, err
}
chaincodeDeploymentSpec := &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec, CodePackage: codePackageBytes}
return chaincodeDeploymentSpec, nil
}
func initialize() {
//use a different address than what we usually use for "peer"
//we override the peerAddress set in chaincode_support.go
peerAddress := "0.0.0.0:21212"
var opts []grpc.ServerOption
grpcServer := grpc.NewServer(opts...)