Skip to content

Commit

Permalink
Merge "[FAB-6230] Resource utilities for peer CLI"
Browse files Browse the repository at this point in the history
  • Loading branch information
mastersingh24 authored and Gerrit Code Review committed Dec 8, 2017
2 parents ad02194 + f601374 commit dd7a116
Show file tree
Hide file tree
Showing 4 changed files with 737 additions and 18 deletions.
327 changes: 327 additions & 0 deletions peer/chaincode/resources.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,327 @@
/*
Copyright IBM Corp. 2016-2017 All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

package chaincode

import (
"bytes"
"fmt"
"strings"
"time"

"github.com/golang/protobuf/proto"
"github.com/hyperledger/fabric/common/resourcesconfig"
"github.com/hyperledger/fabric/common/util"
"github.com/hyperledger/fabric/core/chaincode/shim"
"github.com/hyperledger/fabric/core/scc/lscc"
"github.com/hyperledger/fabric/protos/common"
"github.com/hyperledger/fabric/protos/peer"
"github.com/hyperledger/fabric/protos/utils"
"github.com/pkg/errors"
"golang.org/x/net/context"
)

type channelVersion uint8

const (
v1 = iota
v11
)

// SignatureSupport creates signature headers, signs messages,
// and also serializes its identity to bytes
type SignatureSupport interface {
// Sign the message
Sign(msg []byte) ([]byte, error)

// Serialize converts an identity to bytes
Serialize() ([]byte, error)

// NewSignatureHeader creates a new signature header
NewSignatureHeader() (*common.SignatureHeader, error)
}

// deltaComputer computes the delta from a starting config to a target config
type deltaComputer func(original, updated *common.Config) (*common.ConfigUpdate, error)

type sendInitTransaction func() error

type ccUpdate struct {
policy *common.SignaturePolicyEnvelope
computeDelta deltaComputer
SignatureSupport
ccName string
oldConfig *common.Config
newConfig *common.Config
chainID string
hash []byte
validation string
endorsement string
version string
}

// assembleProposal assembles a SignedProposal given parameters
func assembleProposal(ss SignatureSupport, channel string, targetCC string, function string, args ...string) (*peer.SignedProposal, error) {
var invocation *peer.ChaincodeInvocationSpec
if len(args) == 0 {
invocation = &peer.ChaincodeInvocationSpec{
ChaincodeSpec: &peer.ChaincodeSpec{
Type: peer.ChaincodeSpec_Type(peer.ChaincodeSpec_Type_value["GOLANG"]),
ChaincodeId: &peer.ChaincodeID{Name: targetCC},
Input: &peer.ChaincodeInput{Args: [][]byte{[]byte(function)}},
},
}
} else {
invocation = &peer.ChaincodeInvocationSpec{
ChaincodeSpec: &peer.ChaincodeSpec{
Type: peer.ChaincodeSpec_Type(peer.ChaincodeSpec_Type_value["GOLANG"]),
ChaincodeId: &peer.ChaincodeID{Name: targetCC},
Input: &peer.ChaincodeInput{Args: [][]byte{[]byte(function), []byte(args[0])}},
},
}
}

var prop *peer.Proposal
c, err := ss.Serialize()
if err != nil {
return nil, err
}
prop, _, err = utils.CreateProposalFromCIS(common.HeaderType_ENDORSER_TRANSACTION, channel, invocation, c)
if err != nil {
return nil, errors.New(fmt.Sprintf("Cannot create proposal, due to %s", err))
}
return utils.GetSignedProposal(prop, ss)
}

// fetchCCID fetches the ID of the chaincode from LSCC
func fetchCCID(ss SignatureSupport, ec peer.EndorserClient, name, version string) ([]byte, error) {
sp, err := assembleProposal(ss, "", "lscc", lscc.GETINSTALLEDCHAINCODES)
if err != nil {
return nil, err
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
resp, err := ec.ProcessProposal(ctx, sp)
if err != nil {
return nil, err
}
if resp.Response.Status != shim.OK {
return nil, errors.New(resp.Response.Message)
}
qr := &peer.ChaincodeQueryResponse{}
if err := proto.Unmarshal(resp.Response.Payload, qr); err != nil {
return nil, err
}
for _, cc := range qr.Chaincodes {
if cc.Name == name && cc.Version == version {
return cc.Id, nil
}
}
return nil, errors.Errorf("chaincode with name %s and version %s wasn't found", name, version)
}

// fetchResourceConfig fetches the resource config from the peer, if applicable.
// else, it returns the channel version to be v1.0, or an error upon failure
func fetchResourceConfig(ec peer.EndorserClient, ss SignatureSupport, channel string) (channelVersion, *common.Config, error) {
sp, err := assembleProposal(ss, channel, "cscc", "GetConfigTree", channel)
if err != nil {
logger.Warning("Cannot determine peer version, Proposal assembly failed:", err)
return 0, nil, err
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
resp, err := ec.ProcessProposal(ctx, sp)

if err != nil {
// ProcessProposal may return:
// Case 1: An error, that isn't associated with CSCC chaincode execution (i.e RPC error)
if !strings.Contains(err.Error(), "chaincode") {
logger.Warning("Cannot determine peer version, CSCC returned:", err)
return 0, nil, err
}
// Case 2: An error, that is associated with CSCC chaincode execution
logger.Debug("Peer returned:", err.Error(), "assuming it to be an lscc channel")
return v1, nil, nil
}

if resp == nil || resp.Response == nil {
return 0, nil, errors.Errorf("empty response")
}

// Case 3: A response, with response of a non OK status
if resp.Response.Status != shim.OK {
logger.Debug("Peer returned:", resp.Response, "assuming it to be an lscc channel")
return v1, nil, nil
}
conf := &peer.ConfigTree{}

// Case 4: A response, with an OK status but with an invalid Config payload
if err := proto.Unmarshal(resp.Response.Payload, conf); err != nil || conf.ResourcesConfig.ChannelGroup == nil {
logger.Warning("Peer returned malformed config", conf)
return v1, nil, nil
}

// case 5: A response with an OK status but with a nil groups inside the channel group,
// or with a missing chaincodeGroupKey
if g := conf.ResourcesConfig.ChannelGroup.Groups; g == nil || g[resourcesconfig.ChaincodesGroupKey] == nil {
return v1, nil, nil
}

// Case 6: A response, with an OK status and a valid config
logger.Debug("Peer returned config, assuming it is a config based lifecycle channel")
return v11, conf.ResourcesConfig, nil
}

func ccGroup(ccName string, version string, validation string, endorsement string, hash []byte, modpolicies map[string]string, policy *common.SignaturePolicyEnvelope) *common.ConfigGroup {
logger.Infof("creating ccGroup using %s and %s", validation, endorsement)
var vsccArg []byte
if validation == "" {
validation = "vscc"
}
if endorsement == "" {
endorsement = "escc"
}
if validation == "static-endorsement-policy" {
vsccArg = utils.MarshalOrPanic(&peer.VSCCArgs{
EndorsementPolicyRef: fmt.Sprintf("/Resources/Chaincodes/%s/Endorsement", ccName),
})
}
if validation == "vscc" {
logger.Infof("Setting VSCC arg to simple policy, using %s and %s", validation, endorsement)
vsccArg = utils.MarshalOrPanic(policy)
}
return &common.ConfigGroup{
Policies: map[string]*common.ConfigPolicy{
// TODO: make a constant in some other package
"Endorsement": {
ModPolicy: modpolicies["[Policy] Endorsement"],
Policy: &common.Policy{
Type: int32(common.Policy_SIGNATURE),
Value: utils.MarshalOrPanic(policy),
},
},
},
ModPolicy: modpolicies["Base"],
Values: map[string]*common.ConfigValue{
// TODO: make a constant in some other package
"ChaincodeIdentifier": {
ModPolicy: modpolicies["ChaincodeIdentifier"],
Value: utils.MarshalOrPanic(&peer.ChaincodeIdentifier{
Version: version,
Hash: hash,
}),
},
// TODO: make a constant in some other package
"ChaincodeValidation": {
ModPolicy: modpolicies["ChaincodeValidation"],
Value: utils.MarshalOrPanic(&peer.ChaincodeValidation{
Name: validation,
Argument: vsccArg,
}),
},
// TODO: make a constant in some other package
"ChaincodeEndorsement": {
ModPolicy: modpolicies["ChaincodeEndorsement"],
Value: utils.MarshalOrPanic(&peer.ChaincodeEndorsement{
Name: endorsement,
}),
},
},
}
}

func (update ccUpdate) addChaincode() {
ccGrp, exists := update.newConfig.ChannelGroup.Groups[resourcesconfig.ChaincodesGroupKey]
if !exists {
// We shouldn't reach here, because we classify such a resource config as a v1 channel,
// thus we shouldn't even attempt to modify the chaincode definitions, hence not reach this function.
logger.Panic("Programming error: chaincodes group doesn't exist")
}
// In case no Groups, make our own
if ccGrp.Groups == nil {
logger.Debug("Creating a Groups key for", resourcesconfig.ChaincodesGroupKey)
ccGrp.Groups = make(map[string]*common.ConfigGroup)
}
modPolicies := getModPolicies(ccGrp, update)
ccg := ccGroup(update.ccName, update.version, update.validation, update.endorsement, update.hash, modPolicies, update.policy)
ccGrp.Groups[update.ccName] = ccg
}

func (update ccUpdate) newConfigUpdate(cfgUpdate *common.ConfigUpdate) *common.ConfigUpdateEnvelope {
newConfigUpdateEnv := &common.ConfigUpdateEnvelope{
ConfigUpdate: utils.MarshalOrPanic(cfgUpdate),
}
update.appendSignature(newConfigUpdateEnv)
return newConfigUpdateEnv
}

func (update ccUpdate) appendSignature(env *common.ConfigUpdateEnvelope) *common.ConfigUpdateEnvelope {
sigHdr, err := update.NewSignatureHeader()
if err != nil {
logger.Panic(err)
}
// First iterate over the signatures and see if we already signed this envelope
for _, sig := range env.Signatures {
sh, err := utils.GetSignatureHeader(sig.SignatureHeader)
if err != nil {
logger.Panicf("signature header invalid: %v", err)
}
logger.Info("Found our own signature header, skipping appending our signature...")
if bytes.Equal(sigHdr.Creator, sh.Creator) {
return env
}
}
configSig := &common.ConfigSignature{
SignatureHeader: utils.MarshalOrPanic(sigHdr),
}
configSig.Signature, err = update.Sign(util.ConcatenateBytes(configSig.SignatureHeader, env.ConfigUpdate))
if err != nil {
logger.Panicf("failed signing config update: %v", err)
}
env.Signatures = append(env.Signatures, configSig)
return env
}

func (update ccUpdate) updateIntoEnvelope(updateEnv *common.ConfigUpdateEnvelope) *common.Envelope {
env, err := utils.CreateSignedEnvelope(common.HeaderType_PEER_RESOURCE_UPDATE, update.chainID, update, updateEnv, 0, 0)
if err != nil {
logger.Panic(err)
}
return env
}

func (update ccUpdate) buildCCUpdateEnvelope() *common.Envelope {
update.newConfig = proto.Clone(update.oldConfig).(*common.Config)
update.addChaincode()
cfgUpdate, err := update.computeDelta(update.oldConfig, update.newConfig)
if err != nil {
logger.Panic(err)
}
cfgUpdate.ChannelId = update.chainID
newConfigUpdateEnv := update.newConfigUpdate(cfgUpdate)
return update.updateIntoEnvelope(newConfigUpdateEnv)
}

func getModPolicies(ccGrp *common.ConfigGroup, update ccUpdate) map[string]string {
// By default, it's the channel admins
modPolicies := map[string]string{
"Base": "/Channel/Application/Admins",
"ChaincodeIdentifier": "/Channel/Application/Admins",
"ChaincodeValidation": "/Channel/Application/Admins",
"ChaincodeEndorsement": "/Channel/Application/Admins",
"[Policy] Endorsement": "/Channel/Application/Admins",
}
// If the chaincode has already been configured before,
// we would want (by default) to preserve its modification policy
if oldChaincodeGroup := ccGrp.Groups[update.ccName]; oldChaincodeGroup != nil {
modPolicies["Base"] = oldChaincodeGroup.ModPolicy
modPolicies["ChaincodeIdentifier"] = oldChaincodeGroup.Values["ChaincodeIdentifier"].ModPolicy
modPolicies["ChaincodeValidation"] = oldChaincodeGroup.Values["ChaincodeValidation"].ModPolicy
modPolicies["ChaincodeEndorsement"] = oldChaincodeGroup.Values["ChaincodeEndorsement"].ModPolicy
modPolicies["[Policy] Endorsement"] = oldChaincodeGroup.Policies["Endorsement"].ModPolicy
}
return modPolicies
}
Loading

0 comments on commit dd7a116

Please sign in to comment.