Skip to content

Commit 4d72057

Browse files
author
Jason Yellick
committed
[FAB-2201] Overlay writeset onto existing config
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 <[email protected]>
1 parent 75327ff commit 4d72057

File tree

10 files changed

+212
-114
lines changed

10 files changed

+212
-114
lines changed

common/configtx/api/api.go

+5-2
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,13 @@ type Manager interface {
8484
Resources
8585

8686
// Apply attempts to apply a configtx to become the new config
87-
Apply(configtx *cb.ConfigEnvelope) error
87+
Apply(configtx *cb.ConfigUpdateEnvelope) error
8888

8989
// Validate attempts to validate a new configtx against the current config state
90-
Validate(configtx *cb.ConfigEnvelope) error
90+
Validate(configtx *cb.ConfigUpdateEnvelope) error
91+
92+
// ConfigEnvelope returns the *cb.ConfigEnvelope from the last successful Apply
93+
ConfigEnvelope() *cb.ConfigEnvelope
9194

9295
// ChainID retrieves the chain ID associated with this manager
9396
ChainID() string

common/configtx/configmap.go

+58-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ const (
3333
PathSeparator = "/"
3434
)
3535

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

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

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

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

101103
return nil
102104
}
105+
106+
// configMapToConfig is intended to be called from outside this file
107+
// It takes a configMap and converts it back into a *cb.ConfigGroup structure
108+
func configMapToConfig(configMap map[string]comparable) (*cb.ConfigGroup, error) {
109+
rootPath := PathSeparator + RootGroupKey
110+
return recurseConfigMap(rootPath, configMap)
111+
}
112+
113+
// recurseConfigMap is used only internally by configMapToConfig
114+
// Note, this function mutates the cb.Config* entrieswithin configMap, so errors are generally fatal
115+
func recurseConfigMap(path string, configMap map[string]comparable) (*cb.ConfigGroup, error) {
116+
groupPath := GroupPrefix + path
117+
group, ok := configMap[groupPath]
118+
if !ok {
119+
return nil, fmt.Errorf("Missing group at path: %s", groupPath)
120+
}
121+
122+
if group.ConfigGroup == nil {
123+
return nil, fmt.Errorf("ConfigGroup not found at group path", groupPath)
124+
}
125+
126+
for key, _ := range group.Groups {
127+
updatedGroup, err := recurseConfigMap(path+PathSeparator+key, configMap)
128+
if err != nil {
129+
return nil, err
130+
}
131+
group.Groups[key] = updatedGroup
132+
}
133+
134+
for key, _ := range group.Values {
135+
valuePath := ValuePrefix + path + PathSeparator + key
136+
value, ok := configMap[valuePath]
137+
if !ok {
138+
return nil, fmt.Errorf("Missing value at path: %s", valuePath)
139+
}
140+
if value.ConfigValue == nil {
141+
return nil, fmt.Errorf("ConfigValue not found at value path", valuePath)
142+
}
143+
group.Values[key] = value.ConfigValue
144+
}
145+
146+
for key, _ := range group.Policies {
147+
policyPath := PolicyPrefix + path + PathSeparator + key
148+
policy, ok := configMap[policyPath]
149+
if !ok {
150+
return nil, fmt.Errorf("Missing policy at path: %s", policyPath)
151+
}
152+
if policy.ConfigPolicy == nil {
153+
return nil, fmt.Errorf("ConfigPolicy not found at policy path", policyPath)
154+
}
155+
group.Policies[key] = policy.ConfigPolicy
156+
}
157+
158+
return group.ConfigGroup, nil
159+
}

common/configtx/configmap_test.go

+18
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,21 @@ func TestConfigMap(t *testing.T) {
5757
assert.Equal(t, comparable{key: "2DeepValue", path: []string{"Channel", "0DeepGroup", "1DeepGroup"}, ConfigValue: config.Groups["0DeepGroup"].Groups["1DeepGroup"].Values["2DeepValue"]},
5858
confMap["[Values] /Channel/0DeepGroup/1DeepGroup/2DeepValue"])
5959
}
60+
61+
func TestMapConfigBack(t *testing.T) {
62+
config := cb.NewConfigGroup()
63+
config.Groups["0DeepGroup"] = cb.NewConfigGroup()
64+
config.Values["0DeepValue1"] = &cb.ConfigValue{}
65+
config.Values["0DeepValue2"] = &cb.ConfigValue{}
66+
config.Groups["0DeepGroup"].Policies["1DeepPolicy"] = &cb.ConfigPolicy{}
67+
config.Groups["0DeepGroup"].Groups["1DeepGroup"] = cb.NewConfigGroup()
68+
config.Groups["0DeepGroup"].Groups["1DeepGroup"].Values["2DeepValue"] = &cb.ConfigValue{}
69+
70+
confMap, err := mapConfig(config)
71+
assert.NoError(t, err, "Should not have errored building map")
72+
73+
newConfig, err := configMapToConfig(confMap)
74+
assert.NoError(t, err, "Should not have errored building config")
75+
76+
assert.Equal(t, config, newConfig, "Should have transformed config map back from confMap")
77+
}

common/configtx/manager.go

+57-17
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ type configManager struct {
5757
config map[string]comparable
5858
callOnUpdate []func(api.Manager)
5959
initializer api.Initializer
60+
configEnv *cb.ConfigEnvelope
6061
}
6162

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

161-
err = cm.Apply(configtx)
162-
162+
err = cm.Apply(configtx.LastUpdate)
163163
if err != nil {
164164
return nil, fmt.Errorf("Error applying config transaction: %s", err)
165165
}
@@ -210,14 +210,16 @@ func (cm *configManager) proposeConfig(config map[string]comparable) error {
210210
return nil
211211
}
212212

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

216-
if configtx.LastUpdate == nil {
217-
return nil, fmt.Errorf("Must have ConfigEnvelope.LastUpdate set")
218+
if configUpdateEnv == nil {
219+
return nil, fmt.Errorf("Cannot process nil ConfigUpdateEnvelope")
218220
}
219221

220-
config, err := UnmarshalConfigUpdate(configtx.LastUpdate.ConfigUpdate)
222+
config, err := UnmarshalConfigUpdate(configUpdateEnv.ConfigUpdate)
221223
if err != nil {
222224
return nil, err
223225
}
@@ -227,7 +229,7 @@ func (cm *configManager) processConfig(configtx *cb.ConfigEnvelope) (map[string]
227229
return nil, err
228230
}
229231

230-
signedData, err := configtx.LastUpdate.AsSignedData()
232+
signedData, err := configUpdateEnv.AsSignedData()
231233
if err != nil {
232234
return nil, err
233235
}
@@ -253,14 +255,12 @@ func (cm *configManager) processConfig(configtx *cb.ConfigEnvelope) (map[string]
253255
if err != nil {
254256
return nil, err
255257
}
256-
delete(configMap, "[Groups] /Channel") // XXX temporary hack to prevent evaluating groups for modification
257-
258-
if err := cm.proposeConfig(configMap); err != nil {
259-
return nil, err
260-
}
261-
262258
for key, value := range configMap {
263259
logger.Debugf("Processing key %s with value %v", key, value)
260+
if key == "[Groups] /Channel" {
261+
// XXX temporary hack to prevent group evaluation for modification
262+
continue
263+
}
264264

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

311311
return configMap, nil
312+
}
313+
314+
// computeUpdateResult takes a configMap generated by an update and produces a new configMap overlaying it onto the old config
315+
func (cm *configManager) computeUpdateResult(updatedConfig map[string]comparable) map[string]comparable {
316+
newConfigMap := make(map[string]comparable)
317+
for key, value := range cm.config {
318+
newConfigMap[key] = value
319+
}
312320

321+
for key, value := range updatedConfig {
322+
newConfigMap[key] = value
323+
}
324+
return newConfigMap
313325
}
314326

315-
// Validate attempts to validate a new configtx against the current config state
316-
func (cm *configManager) Validate(configtx *cb.ConfigEnvelope) error {
327+
func (cm *configManager) processConfig(configtx *cb.ConfigUpdateEnvelope) (map[string]comparable, error) {
317328
cm.beginHandlers()
329+
configMap, err := cm.authorizeUpdate(configtx)
330+
if err != nil {
331+
return nil, err
332+
}
333+
computedResult := cm.computeUpdateResult(configMap)
334+
if err := cm.proposeConfig(computedResult); err != nil {
335+
return nil, err
336+
}
337+
return computedResult, nil
338+
}
339+
340+
// Validate attempts to validate a new configtx against the current config state
341+
func (cm *configManager) Validate(configtx *cb.ConfigUpdateEnvelope) error {
318342
_, err := cm.processConfig(configtx)
319343
cm.rollbackHandlers()
320344
return err
321345
}
322346

323347
// Apply attempts to apply a configtx to become the new config
324-
func (cm *configManager) Apply(configtx *cb.ConfigEnvelope) error {
325-
cm.beginHandlers()
348+
func (cm *configManager) Apply(configtx *cb.ConfigUpdateEnvelope) error {
326349
configMap, err := cm.processConfig(configtx)
327350
if err != nil {
328351
cm.rollbackHandlers()
@@ -331,9 +354,26 @@ func (cm *configManager) Apply(configtx *cb.ConfigEnvelope) error {
331354
cm.config = configMap
332355
cm.sequence++
333356
cm.commitHandlers()
357+
channelGroup, err := configMapToConfig(configMap)
358+
if err != nil {
359+
logger.Panicf("Config was validated and applied, but could not be transformed back into proto form: %s", err)
360+
}
361+
362+
cm.configEnv = &cb.ConfigEnvelope{
363+
Config: &cb.Config{
364+
// XXX add header
365+
Channel: channelGroup,
366+
},
367+
LastUpdate: configtx,
368+
}
334369
return nil
335370
}
336371

372+
// ConfigEnvelope retrieve the current ConfigEnvelope, generated after the last successfully applied configuration
373+
func (cm *configManager) ConfigEnvelope() *cb.ConfigEnvelope {
374+
return cm.configEnv
375+
}
376+
337377
// ChainID retrieves the chain ID associated with this manager
338378
func (cm *configManager) ChainID() string {
339379
return cm.chainID

0 commit comments

Comments
 (0)