-
Notifications
You must be signed in to change notification settings - Fork 8.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
Showing
3 changed files
with
801 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
Oops, something went wrong.