Skip to content

Commit

Permalink
[FAB-2638] configtx inspection to include policies
Browse files Browse the repository at this point in the history
https://jira.hyperledger.org/browse/FAB-2638

The configtxgen tool can currently dump the contents of a config block
or config transaction to JSON.  It however only dumps the config values
and not the policies.  This CR enhances the tool to additionally dump
the deserialized policy values for inspection.

Change-Id: I64f40763c3db8a81ffb2120a21d8e7fdabf0f96d
Signed-off-by: Jason Yellick <jyellick@us.ibm.com>
  • Loading branch information
Jason Yellick committed Mar 7, 2017
1 parent 54dc537 commit 1066230
Show file tree
Hide file tree
Showing 11 changed files with 113 additions and 49 deletions.
10 changes: 5 additions & 5 deletions common/cauthdsl/policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,24 +39,24 @@ func NewPolicyProvider(deserializer msp.IdentityDeserializer) policies.Provider
}

// NewPolicy creates a new policy based on the policy bytes
func (pr *provider) NewPolicy(data []byte) (policies.Policy, error) {
func (pr *provider) NewPolicy(data []byte) (policies.Policy, proto.Message, error) {
sigPolicy := &cb.SignaturePolicyEnvelope{}
if err := proto.Unmarshal(data, sigPolicy); err != nil {
return nil, fmt.Errorf("Error unmarshaling to SignaturePolicy: %s", err)
return nil, nil, fmt.Errorf("Error unmarshaling to SignaturePolicy: %s", err)
}

if sigPolicy.Version != 0 {
return nil, fmt.Errorf("This evaluator only understands messages of version 0, but version was %d", sigPolicy.Version)
return nil, nil, fmt.Errorf("This evaluator only understands messages of version 0, but version was %d", sigPolicy.Version)
}

compiled, err := compile(sigPolicy.Policy, sigPolicy.Identities, pr.deserializer)
if err != nil {
return nil, err
return nil, nil, err
}

return &policy{
evaluator: compiled,
}, nil
}, sigPolicy, nil

}

Expand Down
2 changes: 1 addition & 1 deletion common/cauthdsl/policy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ func makePolicySource(policyResult bool) *cb.Policy {

func addPolicy(manager policies.Proposer, id string, policy *cb.Policy) {
manager.BeginPolicyProposals(id, nil)
err := manager.ProposePolicy(id, id, &cb.ConfigPolicy{
_, err := manager.ProposePolicy(id, id, &cb.ConfigPolicy{
Policy: policy,
})
if err != nil {
Expand Down
4 changes: 3 additions & 1 deletion common/configtx/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import (
"github.com/hyperledger/fabric/common/policies"
"github.com/hyperledger/fabric/msp"
cb "github.com/hyperledger/fabric/protos/common"

"github.com/golang/protobuf/proto"
)

// Manager provides a mechanism to query and update config
Expand Down Expand Up @@ -81,7 +83,7 @@ type PolicyHandler interface {

BeginConfig(tx interface{}, groups []string) ([]PolicyHandler, error)

ProposePolicy(tx interface{}, key string, path []string, policy *cb.ConfigPolicy) error
ProposePolicy(tx interface{}, key string, path []string, policy *cb.ConfigPolicy) (proto.Message, error)
}

// Proposer contains the references necesssary to appropriately unmarshal
Expand Down
66 changes: 52 additions & 14 deletions common/configtx/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,14 @@ func NewConfigResult(config *cb.ConfigGroup, proposer api.Proposer) (ConfigResul
}

type configResult struct {
tx interface{}
groupName string
group *cb.ConfigGroup
valueHandler config.ValueProposer
policyHandler policies.Proposer
subResults []*configResult
deserializedValues map[string]proto.Message
tx interface{}
groupName string
group *cb.ConfigGroup
valueHandler config.ValueProposer
policyHandler policies.Proposer
subResults []*configResult
deserializedValues map[string]proto.Message
deserializedPolicies map[string]proto.Message
}

func (cr *configResult) JSON() string {
Expand Down Expand Up @@ -101,6 +102,40 @@ func (cr *configResult) bufferJSON(buffer *bytes.Buffer) {
// },
buffer.WriteString("},")

count = 0
// "Policies": {
buffer.WriteString("\"Policies\": {")
for key, policy := range cr.group.Policies {
// "Key": {
buffer.WriteString("\"")
buffer.WriteString(key)
buffer.WriteString("\": {")
// "Version": "X",
buffer.WriteString("\"Version\":\"")
buffer.WriteString(fmt.Sprintf("%d", policy.Version))
buffer.WriteString("\",")
// "ModPolicy": "foo",
buffer.WriteString("\"ModPolicy\":\"")
buffer.WriteString(policy.ModPolicy)
buffer.WriteString("\",")
// "Policy": {
buffer.WriteString("\"Policy\":{")
// "PolicyType" :
buffer.WriteString(fmt.Sprintf("\"PolicyType\":\"%d\",", policy.Policy.Type))
// "Policy" : policyAsJSON
buffer.WriteString("\"Policy\":")
jpb.Marshal(buffer, cr.deserializedPolicies[key])
// }
// },
buffer.WriteString("}}")
count++
if count < len(cr.group.Policies) {
buffer.WriteString(",")
}
}
// },
buffer.WriteString("},")

// "Groups": {
count = 0
buffer.WriteString("\"Groups\": {")
Expand Down Expand Up @@ -181,22 +216,25 @@ func proposeGroup(result *configResult) error {
}

for key, policy := range result.group.Policies {
if err := result.policyHandler.ProposePolicy(result.tx, key, policy); err != nil {
policy, err := result.policyHandler.ProposePolicy(result.tx, key, policy)
if err != nil {
result.rollback()
return err
}
result.deserializedPolicies[key] = policy
}

result.subResults = make([]*configResult, 0, len(subGroups))

for i, subGroup := range subGroups {
result.subResults = append(result.subResults, &configResult{
tx: result.tx,
groupName: result.groupName + "/" + subGroup,
group: result.group.Groups[subGroup],
valueHandler: subValueHandlers[i],
policyHandler: subPolicyHandlers[i],
deserializedValues: make(map[string]proto.Message),
tx: result.tx,
groupName: result.groupName + "/" + subGroup,
group: result.group.Groups[subGroup],
valueHandler: subValueHandlers[i],
policyHandler: subPolicyHandlers[i],
deserializedValues: make(map[string]proto.Message),
deserializedPolicies: make(map[string]proto.Message),
})

if err := proposeGroup(result.subResults[i]); err != nil {
Expand Down
15 changes: 13 additions & 2 deletions common/configtx/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,21 +44,33 @@ func TestJSON(t *testing.T) {
Values: map[string]*cb.ConfigValue{
"inner1": &cb.ConfigValue{ModPolicy: "mod3"},
},
Policies: map[string]*cb.ConfigPolicy{
"policy1": &cb.ConfigPolicy{Policy: &cb.Policy{}, ModPolicy: "mod1"},
},
},
deserializedValues: map[string]proto.Message{
"inner1": &ab.ConsensusType{Type: "inner1"},
},
deserializedPolicies: map[string]proto.Message{
"policy1": &ab.ConsensusType{Type: "policy1"},
},
},
&configResult{
groupName: "innerGroup2",
group: &cb.ConfigGroup{
Values: map[string]*cb.ConfigValue{
"inner2": &cb.ConfigValue{ModPolicy: "mod3"},
},
Policies: map[string]*cb.ConfigPolicy{
"policy2": &cb.ConfigPolicy{Policy: &cb.Policy{Type: 1}, ModPolicy: "mod2"},
},
},
deserializedValues: map[string]proto.Message{
"inner2": &ab.ConsensusType{Type: "inner2"},
},
deserializedPolicies: map[string]proto.Message{
"policy2": &ab.ConsensusType{Type: "policy2"},
},
},
},
deserializedValues: map[string]proto.Message{
Expand All @@ -69,11 +81,10 @@ func TestJSON(t *testing.T) {
buffer := &bytes.Buffer{}
assert.NoError(t, json.Indent(buffer, []byte(cr.JSON()), "", ""), "JSON should parse nicely")

expected := "{\"rootGroup\":{\"Values\":{\"outer\":{\"Version\":\"1\",\"ModPolicy\":\"mod1\",\"Value\":{\"type\":\"outer\"}}},\"Groups\":{\"innerGroup1\":{\"Values\":{\"inner1\":{\"Version\":\"0\",\"ModPolicy\":\"mod3\",\"Value\":{\"type\":\"inner1\"}}},\"Groups\":{}},\"innerGroup2\":{\"Values\":{\"inner2\":{\"Version\":\"0\",\"ModPolicy\":\"mod3\",\"Value\":{\"type\":\"inner2\"}}},\"Groups\":{}}}}}"
expected := "{\"rootGroup\":{\"Values\":{\"outer\":{\"Version\":\"1\",\"ModPolicy\":\"mod1\",\"Value\":{\"type\":\"outer\"}}},\"Policies\":{},\"Groups\":{\"innerGroup1\":{\"Values\":{\"inner1\":{\"Version\":\"0\",\"ModPolicy\":\"mod3\",\"Value\":{\"type\":\"inner1\"}}},\"Policies\":{\"policy1\":{\"Version\":\"0\",\"ModPolicy\":\"mod1\",\"Policy\":{\"PolicyType\":\"0\",\"Policy\":{\"type\":\"policy1\"}}}},\"Groups\":{}},\"innerGroup2\":{\"Values\":{\"inner2\":{\"Version\":\"0\",\"ModPolicy\":\"mod3\",\"Value\":{\"type\":\"inner2\"}}},\"Policies\":{\"policy2\":{\"Version\":\"0\",\"ModPolicy\":\"mod2\",\"Policy\":{\"PolicyType\":\"1\",\"Policy\":{\"type\":\"policy2\"}}}},\"Groups\":{}}}}}"

// Remove all newlines and spaces from the JSON
compactedJSON := strings.Replace(strings.Replace(buffer.String(), "\n", "", -1), " ", "", -1)

assert.Equal(t, expected, compactedJSON)

}
6 changes: 4 additions & 2 deletions common/configtx/initializer.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import (
"github.com/hyperledger/fabric/common/policies"
"github.com/hyperledger/fabric/msp"
cb "github.com/hyperledger/fabric/protos/common"

"github.com/golang/protobuf/proto"
)

type resources struct {
Expand Down Expand Up @@ -94,8 +96,8 @@ func (p *policyProposerRoot) BeginPolicyProposals(tx interface{}, groups []strin
return []policies.Proposer{p.policyManager}, nil
}

func (i *policyProposerRoot) ProposePolicy(tx interface{}, key string, policy *cb.ConfigPolicy) error {
return fmt.Errorf("Programming error, this should never be invoked")
func (i *policyProposerRoot) ProposePolicy(tx interface{}, key string, policy *cb.ConfigPolicy) (proto.Message, error) {
return nil, fmt.Errorf("Programming error, this should never be invoked")
}

// PreCommit is a no-op and returns nil
Expand Down
10 changes: 8 additions & 2 deletions common/configtx/tool/configtxgen/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,10 @@ func doInspectBlock(inspectBlock string) error {
}

buffer := &bytes.Buffer{}
json.Indent(buffer, []byte(configResult.JSON()), "", " ")
err = json.Indent(buffer, []byte(configResult.JSON()), "", " ")
if err != nil {
return fmt.Errorf("Error in output JSON (usually a programming bug): %s", err)
}

fmt.Printf("Config for channel: %s at sequence %d\n", header.ChannelId, configEnvelope.Config.Sequence)

Expand Down Expand Up @@ -170,7 +173,10 @@ func doInspectChannelCreateTx(inspectChannelCreateTx string) error {
}

buffer := &bytes.Buffer{}
json.Indent(buffer, []byte(configResult.JSON()), "", " ")
err = json.Indent(buffer, []byte(configResult.JSON()), "", " ")
if err != nil {
return fmt.Errorf("Error in output JSON (usually a programming bug): %s", err)
}

fmt.Printf("Config for channel: %s\n", header.ChannelId)

Expand Down
4 changes: 2 additions & 2 deletions common/mocks/configtx/configtx.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,10 +110,10 @@ type PolicyProposer struct {
}

// ProposeConfig sets LastKey to key, LastPath to path, and LastPolicy to configPolicy, returning ErrorForProposedConfig
func (pp *PolicyProposer) ProposePolicy(tx interface{}, key string, configPolicy *cb.ConfigPolicy) error {
func (pp *PolicyProposer) ProposePolicy(tx interface{}, key string, configPolicy *cb.ConfigPolicy) (proto.Message, error) {
pp.LastKey = key
pp.LastPolicy = configPolicy
return pp.ErrorForProposePolicy
return nil, pp.ErrorForProposePolicy
}

// BeginConfig will be removed in the future
Expand Down
25 changes: 15 additions & 10 deletions common/policies/policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (

cb "github.com/hyperledger/fabric/protos/common"

"github.com/golang/protobuf/proto"
logging "github.com/op/go-logging"
)

Expand Down Expand Up @@ -86,8 +87,9 @@ type Proposer interface {
// BeginPolicyProposals starts a policy update transaction
BeginPolicyProposals(tx interface{}, groups []string) ([]Proposer, error)

// ProposePolicy createss a pending policy update from a ConfigPolicy
ProposePolicy(tx interface{}, name string, policy *cb.ConfigPolicy) error
// ProposePolicy createss a pending policy update from a ConfigPolicy and returns the deserialized
// value of the Policy representation
ProposePolicy(tx interface{}, name string, policy *cb.ConfigPolicy) (proto.Message, error)

// RollbackProposals discards the pending policy updates
RollbackProposals(tx interface{})
Expand All @@ -102,7 +104,7 @@ type Proposer interface {
// Provider provides the backing implementation of a policy
type Provider interface {
// NewPolicy creates a new policy based on the policy bytes
NewPolicy(data []byte) (Policy, error)
NewPolicy(data []byte) (Policy, proto.Message, error)
}

// ChannelPolicyManagerGetter is a support interface
Expand Down Expand Up @@ -324,7 +326,8 @@ func (pm *ManagerImpl) CommitProposals(tx interface{}) {
}

// ProposePolicy takes key, path, and ConfigPolicy and registers it in the proposed PolicyManager, or errors
func (pm *ManagerImpl) ProposePolicy(tx interface{}, key string, configPolicy *cb.ConfigPolicy) error {
// It also returns the deserialized policy value for tracking and inspection at the invocation side.
func (pm *ManagerImpl) ProposePolicy(tx interface{}, key string, configPolicy *cb.ConfigPolicy) (proto.Message, error) {
pm.pendingLock.RLock()
pendingConfig, ok := pm.pendingConfig[tx]
pm.pendingLock.RUnlock()
Expand All @@ -334,33 +337,35 @@ func (pm *ManagerImpl) ProposePolicy(tx interface{}, key string, configPolicy *c

policy := configPolicy.Policy
if policy == nil {
return fmt.Errorf("Policy cannot be nil")
return nil, fmt.Errorf("Policy cannot be nil")
}

var cPolicy Policy
var deserialized proto.Message

if policy.Type == int32(cb.Policy_IMPLICIT_META) {
imp, err := newImplicitMetaPolicy(policy.Policy)
if err != nil {
return err
return nil, err
}
pendingConfig.imps = append(pendingConfig.imps, imp)
cPolicy = imp
deserialized = imp.conf
} else {
provider, ok := pm.providers[int32(policy.Type)]
if !ok {
return fmt.Errorf("Unknown policy type: %v", policy.Type)
return nil, fmt.Errorf("Unknown policy type: %v", policy.Type)
}

var err error
cPolicy, err = provider.NewPolicy(policy.Policy)
cPolicy, deserialized, err = provider.NewPolicy(policy.Policy)
if err != nil {
return err
return nil, err
}
}

pendingConfig.policies[key] = cPolicy

logger.Debugf("Proposed new policy %s for %s", key, pm.basePath)
return nil
return deserialized, nil
}
18 changes: 9 additions & 9 deletions common/policies/policy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ import (

cb "github.com/hyperledger/fabric/protos/common"

"github.com/stretchr/testify/assert"

"github.com/golang/protobuf/proto"
logging "github.com/op/go-logging"
"github.com/stretchr/testify/assert"
)

func init() {
Expand All @@ -32,8 +32,8 @@ func init() {

type mockProvider struct{}

func (mpp mockProvider) NewPolicy(data []byte) (Policy, error) {
return nil, nil
func (mpp mockProvider) NewPolicy(data []byte) (Policy, proto.Message, error) {
return nil, nil, nil
}

const mockType = int32(0)
Expand All @@ -53,7 +53,7 @@ func TestUnnestedManager(t *testing.T) {
policyNames := []string{"1", "2", "3"}

for _, policyName := range policyNames {
err := m.ProposePolicy(t, policyName, &cb.ConfigPolicy{Policy: &cb.Policy{Type: mockType}})
_, err := m.ProposePolicy(t, policyName, &cb.ConfigPolicy{Policy: &cb.Policy{Type: mockType}})
assert.NoError(t, err)
}

Expand Down Expand Up @@ -90,25 +90,25 @@ func TestNestedManager(t *testing.T) {

policyNames := []string{"n0a", "n0b", "n0c"}
for _, policyName := range policyNames {
err := m.ProposePolicy(t, policyName, &cb.ConfigPolicy{Policy: &cb.Policy{Type: mockType}})
_, err := m.ProposePolicy(t, policyName, &cb.ConfigPolicy{Policy: &cb.Policy{Type: mockType}})
assert.NoError(t, err)
}

n1PolicyNames := []string{"n1a", "n1b", "n1c"}
for _, policyName := range n1PolicyNames {
err := nesting1[0].ProposePolicy(t, policyName, &cb.ConfigPolicy{Policy: &cb.Policy{Type: mockType}})
_, err := nesting1[0].ProposePolicy(t, policyName, &cb.ConfigPolicy{Policy: &cb.Policy{Type: mockType}})
assert.NoError(t, err)
}

n2aPolicyNames := []string{"n2a_1", "n2a_2", "n2a_3"}
for _, policyName := range n2aPolicyNames {
err := nesting2[0].ProposePolicy(t, policyName, &cb.ConfigPolicy{Policy: &cb.Policy{Type: mockType}})
_, err := nesting2[0].ProposePolicy(t, policyName, &cb.ConfigPolicy{Policy: &cb.Policy{Type: mockType}})
assert.NoError(t, err)
}

n2bPolicyNames := []string{"n2b_1", "n2b_2", "n2b_3"}
for _, policyName := range n2bPolicyNames {
err := nesting2[1].ProposePolicy(t, policyName, &cb.ConfigPolicy{Policy: &cb.Policy{Type: mockType}})
_, err := nesting2[1].ProposePolicy(t, policyName, &cb.ConfigPolicy{Policy: &cb.Policy{Type: mockType}})
assert.NoError(t, err)
}

Expand Down
Loading

0 comments on commit 1066230

Please sign in to comment.