Skip to content

Commit

Permalink
[FAB-2201] Overlay writeset onto existing config
Browse files Browse the repository at this point in the history
https://jira.hyperledger.org/browse/FAB-2201

The configtx code currently shortcuts by assuming that the writeset
contains exactly the entire config. In order to support partial updates,
this writeset should be overlayed on top of the existing config, then
transformed back into a new config.

This CR still depends on the writeset containing the entire config, but,
readies for the conversion to a partial writeset.

Change-Id: I35a71a02437865abe4178c47adfb7c5479b86a22
Signed-off-by: Jason Yellick <jyellick@us.ibm.com>
  • Loading branch information
Jason Yellick committed Feb 14, 2017
1 parent 75327ff commit 4d72057
Show file tree
Hide file tree
Showing 10 changed files with 212 additions and 114 deletions.
7 changes: 5 additions & 2 deletions common/configtx/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,13 @@ type Manager interface {
Resources

// Apply attempts to apply a configtx to become the new config
Apply(configtx *cb.ConfigEnvelope) error
Apply(configtx *cb.ConfigUpdateEnvelope) error

// Validate attempts to validate a new configtx against the current config state
Validate(configtx *cb.ConfigEnvelope) error
Validate(configtx *cb.ConfigUpdateEnvelope) error

// ConfigEnvelope returns the *cb.ConfigEnvelope from the last successful Apply
ConfigEnvelope() *cb.ConfigEnvelope

// ChainID retrieves the chain ID associated with this manager
ChainID() string
Expand Down
59 changes: 58 additions & 1 deletion common/configtx/configmap.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const (
PathSeparator = "/"
)

// mapConfig is the only method in this file intended to be called outside this file
// mapConfig is intended to be called outside this file
// it takes a ConfigGroup and generates a map of fqPath to comparables (or error on invalid keys)
func mapConfig(channelGroup *cb.ConfigGroup) (map[string]comparable, error) {
result := make(map[string]comparable)
Expand All @@ -44,6 +44,7 @@ func mapConfig(channelGroup *cb.ConfigGroup) (map[string]comparable, error) {
return result, nil
}

// addToMap is used only internally by mapConfig
func addToMap(cg comparable, result map[string]comparable) error {
var fqPath string

Expand Down Expand Up @@ -74,6 +75,7 @@ func addToMap(cg comparable, result map[string]comparable) error {
return nil
}

// recurseConfig is used only internally by mapConfig
func recurseConfig(result map[string]comparable, path []string, group *cb.ConfigGroup) error {
if err := addToMap(comparable{key: path[len(path)-1], path: path[:len(path)-1], ConfigGroup: group}, result); err != nil {
return err
Expand All @@ -100,3 +102,58 @@ func recurseConfig(result map[string]comparable, path []string, group *cb.Config

return nil
}

// configMapToConfig is intended to be called from outside this file
// It takes a configMap and converts it back into a *cb.ConfigGroup structure
func configMapToConfig(configMap map[string]comparable) (*cb.ConfigGroup, error) {
rootPath := PathSeparator + RootGroupKey
return recurseConfigMap(rootPath, configMap)
}

// recurseConfigMap is used only internally by configMapToConfig
// Note, this function mutates the cb.Config* entrieswithin configMap, so errors are generally fatal
func recurseConfigMap(path string, configMap map[string]comparable) (*cb.ConfigGroup, error) {
groupPath := GroupPrefix + path
group, ok := configMap[groupPath]
if !ok {
return nil, fmt.Errorf("Missing group at path: %s", groupPath)
}

if group.ConfigGroup == nil {
return nil, fmt.Errorf("ConfigGroup not found at group path", groupPath)
}

for key, _ := range group.Groups {
updatedGroup, err := recurseConfigMap(path+PathSeparator+key, configMap)
if err != nil {
return nil, err
}
group.Groups[key] = updatedGroup
}

for key, _ := range group.Values {
valuePath := ValuePrefix + path + PathSeparator + key
value, ok := configMap[valuePath]
if !ok {
return nil, fmt.Errorf("Missing value at path: %s", valuePath)
}
if value.ConfigValue == nil {
return nil, fmt.Errorf("ConfigValue not found at value path", valuePath)
}
group.Values[key] = value.ConfigValue
}

for key, _ := range group.Policies {
policyPath := PolicyPrefix + path + PathSeparator + key
policy, ok := configMap[policyPath]
if !ok {
return nil, fmt.Errorf("Missing policy at path: %s", policyPath)
}
if policy.ConfigPolicy == nil {
return nil, fmt.Errorf("ConfigPolicy not found at policy path", policyPath)
}
group.Policies[key] = policy.ConfigPolicy
}

return group.ConfigGroup, nil
}
18 changes: 18 additions & 0 deletions common/configtx/configmap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,21 @@ func TestConfigMap(t *testing.T) {
assert.Equal(t, comparable{key: "2DeepValue", path: []string{"Channel", "0DeepGroup", "1DeepGroup"}, ConfigValue: config.Groups["0DeepGroup"].Groups["1DeepGroup"].Values["2DeepValue"]},
confMap["[Values] /Channel/0DeepGroup/1DeepGroup/2DeepValue"])
}

func TestMapConfigBack(t *testing.T) {
config := cb.NewConfigGroup()
config.Groups["0DeepGroup"] = cb.NewConfigGroup()
config.Values["0DeepValue1"] = &cb.ConfigValue{}
config.Values["0DeepValue2"] = &cb.ConfigValue{}
config.Groups["0DeepGroup"].Policies["1DeepPolicy"] = &cb.ConfigPolicy{}
config.Groups["0DeepGroup"].Groups["1DeepGroup"] = cb.NewConfigGroup()
config.Groups["0DeepGroup"].Groups["1DeepGroup"].Values["2DeepValue"] = &cb.ConfigValue{}

confMap, err := mapConfig(config)
assert.NoError(t, err, "Should not have errored building map")

newConfig, err := configMapToConfig(confMap)
assert.NoError(t, err, "Should not have errored building config")

assert.Equal(t, config, newConfig, "Should have transformed config map back from confMap")
}
74 changes: 57 additions & 17 deletions common/configtx/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ type configManager struct {
config map[string]comparable
callOnUpdate []func(api.Manager)
initializer api.Initializer
configEnv *cb.ConfigEnvelope
}

func computeSequence(configGroup *cb.ConfigGroup) uint64 {
Expand Down Expand Up @@ -158,8 +159,7 @@ func NewManagerImpl(configtx *cb.ConfigEnvelope, initializer api.Initializer, ca
callOnUpdate: callOnUpdate,
}

err = cm.Apply(configtx)

err = cm.Apply(configtx.LastUpdate)
if err != nil {
return nil, fmt.Errorf("Error applying config transaction: %s", err)
}
Expand Down Expand Up @@ -210,14 +210,16 @@ func (cm *configManager) proposeConfig(config map[string]comparable) error {
return nil
}

func (cm *configManager) processConfig(configtx *cb.ConfigEnvelope) (map[string]comparable, error) {
// authorizeUpdate validates that all modified config has the corresponding modification policies satisfied by the signature set
// it returns a map of the modified config
func (cm *configManager) authorizeUpdate(configUpdateEnv *cb.ConfigUpdateEnvelope) (map[string]comparable, error) {
// XXX as a temporary hack to get the new protos working, we assume entire config is always in the ConfigUpdate.WriteSet

if configtx.LastUpdate == nil {
return nil, fmt.Errorf("Must have ConfigEnvelope.LastUpdate set")
if configUpdateEnv == nil {
return nil, fmt.Errorf("Cannot process nil ConfigUpdateEnvelope")
}

config, err := UnmarshalConfigUpdate(configtx.LastUpdate.ConfigUpdate)
config, err := UnmarshalConfigUpdate(configUpdateEnv.ConfigUpdate)
if err != nil {
return nil, err
}
Expand All @@ -227,7 +229,7 @@ func (cm *configManager) processConfig(configtx *cb.ConfigEnvelope) (map[string]
return nil, err
}

signedData, err := configtx.LastUpdate.AsSignedData()
signedData, err := configUpdateEnv.AsSignedData()
if err != nil {
return nil, err
}
Expand All @@ -253,14 +255,12 @@ func (cm *configManager) processConfig(configtx *cb.ConfigEnvelope) (map[string]
if err != nil {
return nil, err
}
delete(configMap, "[Groups] /Channel") // XXX temporary hack to prevent evaluating groups for modification

if err := cm.proposeConfig(configMap); err != nil {
return nil, err
}

for key, value := range configMap {
logger.Debugf("Processing key %s with value %v", key, value)
if key == "[Groups] /Channel" {
// XXX temporary hack to prevent group evaluation for modification
continue
}

// Ensure the config sequence numbers are correct to prevent replay attacks
var isModified bool
Expand Down Expand Up @@ -309,20 +309,43 @@ func (cm *configManager) processConfig(configtx *cb.ConfigEnvelope) (map[string]
}

return configMap, nil
}

// computeUpdateResult takes a configMap generated by an update and produces a new configMap overlaying it onto the old config
func (cm *configManager) computeUpdateResult(updatedConfig map[string]comparable) map[string]comparable {
newConfigMap := make(map[string]comparable)
for key, value := range cm.config {
newConfigMap[key] = value
}

for key, value := range updatedConfig {
newConfigMap[key] = value
}
return newConfigMap
}

// Validate attempts to validate a new configtx against the current config state
func (cm *configManager) Validate(configtx *cb.ConfigEnvelope) error {
func (cm *configManager) processConfig(configtx *cb.ConfigUpdateEnvelope) (map[string]comparable, error) {
cm.beginHandlers()
configMap, err := cm.authorizeUpdate(configtx)
if err != nil {
return nil, err
}
computedResult := cm.computeUpdateResult(configMap)
if err := cm.proposeConfig(computedResult); err != nil {
return nil, err
}
return computedResult, nil
}

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

// Apply attempts to apply a configtx to become the new config
func (cm *configManager) Apply(configtx *cb.ConfigEnvelope) error {
cm.beginHandlers()
func (cm *configManager) Apply(configtx *cb.ConfigUpdateEnvelope) error {
configMap, err := cm.processConfig(configtx)
if err != nil {
cm.rollbackHandlers()
Expand All @@ -331,9 +354,26 @@ func (cm *configManager) Apply(configtx *cb.ConfigEnvelope) error {
cm.config = configMap
cm.sequence++
cm.commitHandlers()
channelGroup, err := configMapToConfig(configMap)
if err != nil {
logger.Panicf("Config was validated and applied, but could not be transformed back into proto form: %s", err)
}

cm.configEnv = &cb.ConfigEnvelope{
Config: &cb.Config{
// XXX add header
Channel: channelGroup,
},
LastUpdate: configtx,
}
return nil
}

// ConfigEnvelope retrieve the current ConfigEnvelope, generated after the last successfully applied configuration
func (cm *configManager) ConfigEnvelope() *cb.ConfigEnvelope {
return cm.configEnv
}

// ChainID retrieves the chain ID associated with this manager
func (cm *configManager) ChainID() string {
return cm.chainID
Expand Down
Loading

0 comments on commit 4d72057

Please sign in to comment.