Skip to content

Commit

Permalink
Add a Chain Configuration Manager
Browse files Browse the repository at this point in the history
In order to specify genesis configuration, it must be encoded in the
block in a consumable way.  This configuration must then be stored
in a way accessible to any other components which wish to consume this
configuration.  Further, when new configuration is proposed it must be
validated, and applied or rejected depending on its validity.

This changeset creates a configuration manager which handles all these
tasks.  It allows for registering arbitrary configuration handlers for
different configuration types, and has been designed in concert with the
policy manager to allow the policy manager to be plugged in as a
configuration handler so that policy both enforces what configuration
modifications are allowed and is itself a type of configuration.

Because of the complicated nature of verifying that a configuration is
valid (well formed, not forged or replayed, conforms to existing policy,
and satisfies the assorted handlers) this changeset is unfortunately
large, but is needed to achieve complete code and test coverage of
invalid configurations.

This resolves:

https://jira.hyperledger.org/browse/FAB-724

Change-Id: I44cbc3f2e5850d8c22977c923c044d353a9bc453
Signed-off-by: Jason Yellick <jyellick@us.ibm.com>
  • Loading branch information
Jason Yellick committed Oct 26, 2016
1 parent 5274bb1 commit 4db9abf
Show file tree
Hide file tree
Showing 3 changed files with 801 additions and 0 deletions.
67 changes: 67 additions & 0 deletions orderer/common/configtx/bytes_handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
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 configtx

import (
ab "github.com/hyperledger/fabric/orderer/atomicbroadcast"
)

// BytesHandler is a trivial ConfigHandler which simpy tracks the bytes stores in a config
type BytesHandler struct {
config map[string][]byte
proposed map[string][]byte
}

// NewBytesHandler creates a new BytesHandler
func NewBytesHandler() *BytesHandler {
return &BytesHandler{
config: make(map[string][]byte),
}
}

// BeginConfig called when a config proposal is begun
func (bh *BytesHandler) BeginConfig() {
if bh.proposed != nil {
panic("Programming error, called BeginConfig while a proposal was in process")
}
bh.proposed = make(map[string][]byte)
}

// RollbackConfig called when a config proposal is abandoned
func (bh *BytesHandler) RollbackConfig() {
bh.proposed = nil
}

// CommitConfig called when a config proposal is committed
func (bh *BytesHandler) CommitConfig() {
if bh.proposed == nil {
panic("Programming error, called CommitConfig with no proposal in process")
}
bh.config = bh.proposed
bh.proposed = nil
}

// ProposeConfig called when config is added to a proposal
func (bh *BytesHandler) ProposeConfig(configItem *ab.Configuration) error {
bh.proposed[configItem.ID] = configItem.Data
return nil
}

// GetBytes allows the caller to retrieve the bytes for a config
func (bh *BytesHandler) GetBytes(id string) []byte {
return bh.config[id]
}
235 changes: 235 additions & 0 deletions orderer/common/configtx/configtx.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
/*
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 configtx

import (
"bytes"
"fmt"

ab "github.com/hyperledger/fabric/orderer/atomicbroadcast"
"github.com/hyperledger/fabric/orderer/common/policies"

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

// Handler provides a hook which allows other pieces of code to participate in config proposals
type Handler interface {
// BeginConfig called when a config proposal is begun
BeginConfig()

// RollbackConfig called when a config proposal is abandoned
RollbackConfig()

// CommitConfig called when a config proposal is committed
CommitConfig()

// ProposeConfig called when config is added to a proposal
ProposeConfig(configItem *ab.Configuration) error
}

// Manager provides a mechanism to query and update configuration
type Manager interface {
// Apply attempts to apply a configtx to become the new configuration
Apply(configtx *ab.ConfigurationEnvelope) error

// Validate attempts to validate a new configtx against the current config state
Validate(configtx *ab.ConfigurationEnvelope) error
}

// DefaultModificationPolicyID is the ID of the policy used when no other policy can be resolved, for instance when attempting to create a new config item
const DefaultModificationPolicyID = "DefaultModificationPolicy"

type acceptAllPolicy struct{}

func (ap *acceptAllPolicy) Evaluate(msg []byte, sigs []*ab.SignedData) error {
return nil
}

type configurationManager struct {
sequence uint64
chainID []byte
pm policies.Manager
configuration map[ab.Configuration_ConfigurationType]map[string]*ab.Configuration
handlers map[ab.Configuration_ConfigurationType]Handler
}

// NewConfigurationManager creates a new Manager unless an error is encountered
func NewConfigurationManager(configtx *ab.ConfigurationEnvelope, pm policies.Manager, handlers map[ab.Configuration_ConfigurationType]Handler) (Manager, error) {
for ctype := range ab.Configuration_ConfigurationType_name {
if _, ok := handlers[ab.Configuration_ConfigurationType(ctype)]; !ok {
return nil, fmt.Errorf("Must supply a handler for all known types")
}
}

cm := &configurationManager{
sequence: configtx.Sequence - 1,
chainID: configtx.ChainID,
pm: pm,
handlers: handlers,
configuration: makeConfigMap(),
}

err := cm.Apply(configtx)

if err != nil {
return nil, err
}

return cm, nil
}

func makeConfigMap() map[ab.Configuration_ConfigurationType]map[string]*ab.Configuration {
configMap := make(map[ab.Configuration_ConfigurationType]map[string]*ab.Configuration)
for ctype := range ab.Configuration_ConfigurationType_name {
configMap[ab.Configuration_ConfigurationType(ctype)] = make(map[string]*ab.Configuration)
}
return configMap
}

func (cm *configurationManager) beginHandlers() {
for ctype := range ab.Configuration_ConfigurationType_name {
cm.handlers[ab.Configuration_ConfigurationType(ctype)].BeginConfig()
}
}

func (cm *configurationManager) rollbackHandlers() {
for ctype := range ab.Configuration_ConfigurationType_name {
cm.handlers[ab.Configuration_ConfigurationType(ctype)].RollbackConfig()
}
}

func (cm *configurationManager) commitHandlers() {
for ctype := range ab.Configuration_ConfigurationType_name {
cm.handlers[ab.Configuration_ConfigurationType(ctype)].CommitConfig()
}
}

func (cm *configurationManager) processConfig(configtx *ab.ConfigurationEnvelope) (configMap map[ab.Configuration_ConfigurationType]map[string]*ab.Configuration, err error) {
// Verify config is a sequential update to prevent exhausting sequence numbers
if configtx.Sequence != cm.sequence+1 {
return nil, fmt.Errorf("Config sequence number jumped from %d to %d", cm.sequence, configtx.Sequence)
}

// Verify config is intended for this globally unique chain ID
if !bytes.Equal(configtx.ChainID, cm.chainID) {
return nil, fmt.Errorf("Config is for the wrong chain, expected %x, got %x", cm.chainID, configtx.ChainID)
}

defaultModificationPolicy, defaultPolicySet := cm.pm.GetPolicy(DefaultModificationPolicyID)

// If the default modification policy is not set, it indicates this is an uninitialized chain, so be permissive of modification
if !defaultPolicySet {
defaultModificationPolicy = &acceptAllPolicy{}
}

configMap = makeConfigMap()

for _, entry := range configtx.Entries {
// Verify every entry is well formed
config := &ab.Configuration{}
err = proto.Unmarshal(entry.Configuration, config)
if err != nil {
return nil, err
}

// Ensure this configuration was intended for this chain
if !bytes.Equal(config.ChainID, cm.chainID) {
return nil, fmt.Errorf("Config item %v for type %v was not meant for a different chain %x", config.ID, config.Type, config.ChainID)
}

// Get the modification policy for this config item if one was previously specified
// or the default if this is a new config item
var policy policies.Policy
oldItem, ok := cm.configuration[config.Type][config.ID]
if ok {
policy, _ = cm.pm.GetPolicy(oldItem.ModificationPolicy)
} else {
policy = defaultModificationPolicy
}

// Ensure the policy is satisfied
if err = policy.Evaluate(entry.Configuration, entry.Signatures); err != nil {
return nil, err
}

// Ensure the config sequence numbers are correct to prevent replay attacks
isModified := false

if val, ok := cm.configuration[config.Type][config.ID]; ok {
// Config was modified if the LastModified or the Data contents changed
isModified = (val.LastModified != config.LastModified) || !bytes.Equal(config.Data, val.Data)
} else {
if config.LastModified != configtx.Sequence {
return nil, fmt.Errorf("Key %v for type %v was new, but had an older Sequence %d set", config.ID, config.Type, config.LastModified)
}
isModified = true
}

// If a config item was modified, its LastModified must be set correctly
if isModified {
if config.LastModified != configtx.Sequence {
return nil, fmt.Errorf("Key %v for type %v was modified, but its LastModified %d does not equal current configtx Sequence %d", config.ID, config.Type, config.LastModified, configtx.Sequence)
}
}

// Ensure the type handler agrees the config is well formed
err = cm.handlers[config.Type].ProposeConfig(config)
if err != nil {
return nil, err
}

configMap[config.Type][config.ID] = config
}

// Ensure that any config items which used to exist still exist, to prevent implicit deletion
for ctype := range ab.Configuration_ConfigurationType_name {
curMap := cm.configuration[ab.Configuration_ConfigurationType(ctype)]
newMap := configMap[ab.Configuration_ConfigurationType(ctype)]
for id := range curMap {
_, ok := newMap[id]
if !ok {
return nil, fmt.Errorf("Missing key %v for type %v in new configuration", id, ctype)
}

}
}

return configMap, nil

}

// Validate attempts to validate a new configtx against the current config state
func (cm *configurationManager) Validate(configtx *ab.ConfigurationEnvelope) error {
cm.beginHandlers()
_, err := cm.processConfig(configtx)
cm.rollbackHandlers()
return err
}

// Apply attempts to apply a configtx to become the new configuration
func (cm *configurationManager) Apply(configtx *ab.ConfigurationEnvelope) error {
cm.beginHandlers()
configMap, err := cm.processConfig(configtx)
if err != nil {
cm.rollbackHandlers()
return err
}
cm.configuration = configMap
cm.sequence = configtx.Sequence
cm.commitHandlers()
return nil
}
Loading

0 comments on commit 4db9abf

Please sign in to comment.