Skip to content

Commit

Permalink
[FAB-9111] CC convenience layer for state-based EP
Browse files Browse the repository at this point in the history
This implementation of the CC convenience layer interface facilitates
the usage of state-based endorsement. It allows easy modification of
a state-based endorsement policy providing Add/Delete functions that
are based on MSP-IDs and automatically creates the underlying
endorsement policy.

Change-Id: Ic71f17ba6d3a6004e6d5b265bb8cb562eac2eddb
Signed-off-by: Matthias Neugschwandtner <[email protected]>
Signed-off-by: Alessandro Sorniotti <[email protected]>
  • Loading branch information
Matthias Neugschwandtner committed Sep 5, 2018
1 parent a8ea43f commit 63c34a6
Show file tree
Hide file tree
Showing 3 changed files with 198 additions and 4 deletions.
6 changes: 2 additions & 4 deletions core/chaincode/shim/ext/statebased/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,17 @@ const (
RoleTypeMember = RoleType("MEMBER")
// RoleTypePeer identifies an org's peer identity
RoleTypePeer = RoleType("PEER")
// RoleTypeClient identifies an org's client identity
RoleTypeClient = RoleType("CLIENT")
)

// RoleTypeDoesNotExistError is returned by function AddOrgs of
// KeyEndorsementPolicy if a role type that does not match one
// specified above is passed as an argument.
type RoleTypeDoesNotExistError struct {
roleType RoleType
RoleType RoleType
}

func (r *RoleTypeDoesNotExistError) Error() string {
return fmt.Sprintf("Role type %s does not exist", r.roleType)
return fmt.Sprintf("role type %s does not exist", r.RoleType)
}

// KeyEndorsementPolicy provides a set of convenience methods to create and
Expand Down
126 changes: 126 additions & 0 deletions core/chaincode/shim/ext/statebased/statebasedimpl.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/*
Copyright IBM Corp. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

package statebased

import (
"fmt"
"sort"

"github.com/golang/protobuf/proto"
"github.com/hyperledger/fabric/common/cauthdsl"
cb "github.com/hyperledger/fabric/protos/common"
mb "github.com/hyperledger/fabric/protos/msp"
"github.com/hyperledger/fabric/protos/utils"
"github.com/pkg/errors"
)

// stateEP implements the KeyEndorsementPolicy
type stateEP struct {
orgs map[string]mb.MSPRole_MSPRoleType
}

// NewStateEP constructs a state-based endorsement policy from a given
// serialized EP byte array. If the byte array is empty, a new EP is created.
func NewStateEP(policy []byte) (KeyEndorsementPolicy, error) {
s := &stateEP{orgs: make(map[string]mb.MSPRole_MSPRoleType)}
if policy != nil {
spe := &cb.SignaturePolicyEnvelope{}
if err := proto.Unmarshal(policy, spe); err != nil {
return nil, fmt.Errorf("Error unmarshaling to SignaturePolicy: %s", err)
}

err := s.setMSPIDsFromSP(spe)
if err != nil {
return nil, err
}
}
return s, nil
}

// Policy returns the endorsement policy as bytes
func (s *stateEP) Policy() ([]byte, error) {
spe := s.policyFromMSPIDs()
spBytes, err := proto.Marshal(spe)
if err != nil {
return nil, err
}
return spBytes, nil
}

// AddOrgs adds the specified channel orgs to the existing key-level EP
func (s *stateEP) AddOrgs(role RoleType, neworgs ...string) error {
var mspRole mb.MSPRole_MSPRoleType
switch role {
case RoleTypeMember:
mspRole = mb.MSPRole_MEMBER
case RoleTypePeer:
mspRole = mb.MSPRole_PEER
default:
return &RoleTypeDoesNotExistError{RoleType: role}
}

// add new orgs
for _, addorg := range neworgs {
s.orgs[addorg] = mspRole
}

return nil
}

// DelOrgs delete the specified channel orgs from the existing key-level EP
func (s *stateEP) DelOrgs(delorgs ...string) {
for _, delorg := range delorgs {
delete(s.orgs, delorg)
}
}

// ListOrgs returns an array of channel orgs that are required to endorse chnages
func (s *stateEP) ListOrgs() []string {
orgNames := make([]string, 0, len(s.orgs))
for mspid := range s.orgs {
orgNames = append(orgNames, mspid)
}
return orgNames
}

func (s *stateEP) setMSPIDsFromSP(sp *cb.SignaturePolicyEnvelope) error {
// iterate over the identities in this envelope
for _, identity := range sp.Identities {
// this imlementation only supports the ROLE type
if identity.PrincipalClassification == mb.MSPPrincipal_ROLE {
msprole := &mb.MSPRole{}
err := proto.Unmarshal(identity.Principal, msprole)
if err != nil {
return errors.Wrapf(err, "error unmarshaling msp principal")
}
s.orgs[msprole.GetMspIdentifier()] = msprole.GetRole()
}
}
return nil
}

func (s *stateEP) policyFromMSPIDs() *cb.SignaturePolicyEnvelope {
mspids := s.ListOrgs()
sort.Strings(mspids)
principals := make([]*mb.MSPPrincipal, len(mspids))
sigspolicy := make([]*cb.SignaturePolicy, len(mspids))
for i, id := range mspids {
principals[i] = &mb.MSPPrincipal{
PrincipalClassification: mb.MSPPrincipal_ROLE,
Principal: utils.MarshalOrPanic(&mb.MSPRole{Role: s.orgs[id], MspIdentifier: id}),
}
sigspolicy[i] = cauthdsl.SignedBy(int32(i))
}

// create the policy: it requires exactly 1 signature from all of the principals
p := &cb.SignaturePolicyEnvelope{
Version: 0,
Rule: cauthdsl.NOutOf(int32(len(mspids)), sigspolicy),
Identities: principals,
}
return p
}
70 changes: 70 additions & 0 deletions core/chaincode/shim/ext/statebased/statebasedimpl_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
Copyright IBM Corp. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

package statebased_test

import (
"testing"

"github.com/hyperledger/fabric/common/cauthdsl"

"github.com/golang/protobuf/proto"
"github.com/hyperledger/fabric/core/chaincode/shim/ext/statebased"
"github.com/stretchr/testify/assert"
)

func TestAddOrg(t *testing.T) {
// add an org
ep, err := statebased.NewStateEP(nil)
assert.NoError(t, err)
err = ep.AddOrgs(statebased.RoleTypePeer, "Org1")
assert.NoError(t, err)
err = ep.AddOrgs("unknown", "Org1")
assert.Error(t, err)
rtdnee, ok := err.(*statebased.RoleTypeDoesNotExistError)
assert.True(t, ok)
assert.Equal(t, statebased.RoleType("unknown"), rtdnee.RoleType)
epBytes, err := ep.Policy()
assert.NoError(t, err)
expectedEP := cauthdsl.SignedByMspPeer("Org1")
expectedEPBytes, err := proto.Marshal(expectedEP)
assert.NoError(t, err)
assert.Equal(t, expectedEPBytes, epBytes)
}

func TestListOrgs(t *testing.T) {
expectedEP := cauthdsl.SignedByMspPeer("Org1")
expectedEPBytes, err := proto.Marshal(expectedEP)
assert.NoError(t, err)

// retrieve the orgs
ep, err := statebased.NewStateEP(expectedEPBytes)
orgs := ep.ListOrgs()
assert.Equal(t, []string{"Org1"}, orgs)
}

func TestDelAddOrg(t *testing.T) {
expectedEP := cauthdsl.SignedByMspPeer("Org1")
expectedEPBytes, err := proto.Marshal(expectedEP)
assert.NoError(t, err)
ep, err := statebased.NewStateEP(expectedEPBytes)

// retrieve the orgs
orgs := ep.ListOrgs()
assert.Equal(t, []string{"Org1"}, orgs)

// mod the endorsement policy
ep.AddOrgs(statebased.RoleTypePeer, "Org2")
ep.DelOrgs("Org1")

// check whether what is stored is correct
epBytes, err := ep.Policy()
assert.NoError(t, err)
expectedEP = cauthdsl.SignedByMspPeer("Org2")
expectedEPBytes, err = proto.Marshal(expectedEP)
assert.NoError(t, err)
assert.Equal(t, expectedEPBytes, epBytes)
}

0 comments on commit 63c34a6

Please sign in to comment.