Skip to content

Commit 4db9abf

Browse files
author
Jason Yellick
committed
Add a Chain Configuration Manager
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 <[email protected]>
1 parent 5274bb1 commit 4db9abf

File tree

3 files changed

+801
-0
lines changed

3 files changed

+801
-0
lines changed
+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
Copyright IBM Corp. 2016 All Rights Reserved.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package configtx
18+
19+
import (
20+
ab "github.com/hyperledger/fabric/orderer/atomicbroadcast"
21+
)
22+
23+
// BytesHandler is a trivial ConfigHandler which simpy tracks the bytes stores in a config
24+
type BytesHandler struct {
25+
config map[string][]byte
26+
proposed map[string][]byte
27+
}
28+
29+
// NewBytesHandler creates a new BytesHandler
30+
func NewBytesHandler() *BytesHandler {
31+
return &BytesHandler{
32+
config: make(map[string][]byte),
33+
}
34+
}
35+
36+
// BeginConfig called when a config proposal is begun
37+
func (bh *BytesHandler) BeginConfig() {
38+
if bh.proposed != nil {
39+
panic("Programming error, called BeginConfig while a proposal was in process")
40+
}
41+
bh.proposed = make(map[string][]byte)
42+
}
43+
44+
// RollbackConfig called when a config proposal is abandoned
45+
func (bh *BytesHandler) RollbackConfig() {
46+
bh.proposed = nil
47+
}
48+
49+
// CommitConfig called when a config proposal is committed
50+
func (bh *BytesHandler) CommitConfig() {
51+
if bh.proposed == nil {
52+
panic("Programming error, called CommitConfig with no proposal in process")
53+
}
54+
bh.config = bh.proposed
55+
bh.proposed = nil
56+
}
57+
58+
// ProposeConfig called when config is added to a proposal
59+
func (bh *BytesHandler) ProposeConfig(configItem *ab.Configuration) error {
60+
bh.proposed[configItem.ID] = configItem.Data
61+
return nil
62+
}
63+
64+
// GetBytes allows the caller to retrieve the bytes for a config
65+
func (bh *BytesHandler) GetBytes(id string) []byte {
66+
return bh.config[id]
67+
}

orderer/common/configtx/configtx.go

+235
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
/*
2+
Copyright IBM Corp. 2016 All Rights Reserved.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package configtx
18+
19+
import (
20+
"bytes"
21+
"fmt"
22+
23+
ab "github.com/hyperledger/fabric/orderer/atomicbroadcast"
24+
"github.com/hyperledger/fabric/orderer/common/policies"
25+
26+
"github.com/golang/protobuf/proto"
27+
)
28+
29+
// Handler provides a hook which allows other pieces of code to participate in config proposals
30+
type Handler interface {
31+
// BeginConfig called when a config proposal is begun
32+
BeginConfig()
33+
34+
// RollbackConfig called when a config proposal is abandoned
35+
RollbackConfig()
36+
37+
// CommitConfig called when a config proposal is committed
38+
CommitConfig()
39+
40+
// ProposeConfig called when config is added to a proposal
41+
ProposeConfig(configItem *ab.Configuration) error
42+
}
43+
44+
// Manager provides a mechanism to query and update configuration
45+
type Manager interface {
46+
// Apply attempts to apply a configtx to become the new configuration
47+
Apply(configtx *ab.ConfigurationEnvelope) error
48+
49+
// Validate attempts to validate a new configtx against the current config state
50+
Validate(configtx *ab.ConfigurationEnvelope) error
51+
}
52+
53+
// 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
54+
const DefaultModificationPolicyID = "DefaultModificationPolicy"
55+
56+
type acceptAllPolicy struct{}
57+
58+
func (ap *acceptAllPolicy) Evaluate(msg []byte, sigs []*ab.SignedData) error {
59+
return nil
60+
}
61+
62+
type configurationManager struct {
63+
sequence uint64
64+
chainID []byte
65+
pm policies.Manager
66+
configuration map[ab.Configuration_ConfigurationType]map[string]*ab.Configuration
67+
handlers map[ab.Configuration_ConfigurationType]Handler
68+
}
69+
70+
// NewConfigurationManager creates a new Manager unless an error is encountered
71+
func NewConfigurationManager(configtx *ab.ConfigurationEnvelope, pm policies.Manager, handlers map[ab.Configuration_ConfigurationType]Handler) (Manager, error) {
72+
for ctype := range ab.Configuration_ConfigurationType_name {
73+
if _, ok := handlers[ab.Configuration_ConfigurationType(ctype)]; !ok {
74+
return nil, fmt.Errorf("Must supply a handler for all known types")
75+
}
76+
}
77+
78+
cm := &configurationManager{
79+
sequence: configtx.Sequence - 1,
80+
chainID: configtx.ChainID,
81+
pm: pm,
82+
handlers: handlers,
83+
configuration: makeConfigMap(),
84+
}
85+
86+
err := cm.Apply(configtx)
87+
88+
if err != nil {
89+
return nil, err
90+
}
91+
92+
return cm, nil
93+
}
94+
95+
func makeConfigMap() map[ab.Configuration_ConfigurationType]map[string]*ab.Configuration {
96+
configMap := make(map[ab.Configuration_ConfigurationType]map[string]*ab.Configuration)
97+
for ctype := range ab.Configuration_ConfigurationType_name {
98+
configMap[ab.Configuration_ConfigurationType(ctype)] = make(map[string]*ab.Configuration)
99+
}
100+
return configMap
101+
}
102+
103+
func (cm *configurationManager) beginHandlers() {
104+
for ctype := range ab.Configuration_ConfigurationType_name {
105+
cm.handlers[ab.Configuration_ConfigurationType(ctype)].BeginConfig()
106+
}
107+
}
108+
109+
func (cm *configurationManager) rollbackHandlers() {
110+
for ctype := range ab.Configuration_ConfigurationType_name {
111+
cm.handlers[ab.Configuration_ConfigurationType(ctype)].RollbackConfig()
112+
}
113+
}
114+
115+
func (cm *configurationManager) commitHandlers() {
116+
for ctype := range ab.Configuration_ConfigurationType_name {
117+
cm.handlers[ab.Configuration_ConfigurationType(ctype)].CommitConfig()
118+
}
119+
}
120+
121+
func (cm *configurationManager) processConfig(configtx *ab.ConfigurationEnvelope) (configMap map[ab.Configuration_ConfigurationType]map[string]*ab.Configuration, err error) {
122+
// Verify config is a sequential update to prevent exhausting sequence numbers
123+
if configtx.Sequence != cm.sequence+1 {
124+
return nil, fmt.Errorf("Config sequence number jumped from %d to %d", cm.sequence, configtx.Sequence)
125+
}
126+
127+
// Verify config is intended for this globally unique chain ID
128+
if !bytes.Equal(configtx.ChainID, cm.chainID) {
129+
return nil, fmt.Errorf("Config is for the wrong chain, expected %x, got %x", cm.chainID, configtx.ChainID)
130+
}
131+
132+
defaultModificationPolicy, defaultPolicySet := cm.pm.GetPolicy(DefaultModificationPolicyID)
133+
134+
// If the default modification policy is not set, it indicates this is an uninitialized chain, so be permissive of modification
135+
if !defaultPolicySet {
136+
defaultModificationPolicy = &acceptAllPolicy{}
137+
}
138+
139+
configMap = makeConfigMap()
140+
141+
for _, entry := range configtx.Entries {
142+
// Verify every entry is well formed
143+
config := &ab.Configuration{}
144+
err = proto.Unmarshal(entry.Configuration, config)
145+
if err != nil {
146+
return nil, err
147+
}
148+
149+
// Ensure this configuration was intended for this chain
150+
if !bytes.Equal(config.ChainID, cm.chainID) {
151+
return nil, fmt.Errorf("Config item %v for type %v was not meant for a different chain %x", config.ID, config.Type, config.ChainID)
152+
}
153+
154+
// Get the modification policy for this config item if one was previously specified
155+
// or the default if this is a new config item
156+
var policy policies.Policy
157+
oldItem, ok := cm.configuration[config.Type][config.ID]
158+
if ok {
159+
policy, _ = cm.pm.GetPolicy(oldItem.ModificationPolicy)
160+
} else {
161+
policy = defaultModificationPolicy
162+
}
163+
164+
// Ensure the policy is satisfied
165+
if err = policy.Evaluate(entry.Configuration, entry.Signatures); err != nil {
166+
return nil, err
167+
}
168+
169+
// Ensure the config sequence numbers are correct to prevent replay attacks
170+
isModified := false
171+
172+
if val, ok := cm.configuration[config.Type][config.ID]; ok {
173+
// Config was modified if the LastModified or the Data contents changed
174+
isModified = (val.LastModified != config.LastModified) || !bytes.Equal(config.Data, val.Data)
175+
} else {
176+
if config.LastModified != configtx.Sequence {
177+
return nil, fmt.Errorf("Key %v for type %v was new, but had an older Sequence %d set", config.ID, config.Type, config.LastModified)
178+
}
179+
isModified = true
180+
}
181+
182+
// If a config item was modified, its LastModified must be set correctly
183+
if isModified {
184+
if config.LastModified != configtx.Sequence {
185+
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)
186+
}
187+
}
188+
189+
// Ensure the type handler agrees the config is well formed
190+
err = cm.handlers[config.Type].ProposeConfig(config)
191+
if err != nil {
192+
return nil, err
193+
}
194+
195+
configMap[config.Type][config.ID] = config
196+
}
197+
198+
// Ensure that any config items which used to exist still exist, to prevent implicit deletion
199+
for ctype := range ab.Configuration_ConfigurationType_name {
200+
curMap := cm.configuration[ab.Configuration_ConfigurationType(ctype)]
201+
newMap := configMap[ab.Configuration_ConfigurationType(ctype)]
202+
for id := range curMap {
203+
_, ok := newMap[id]
204+
if !ok {
205+
return nil, fmt.Errorf("Missing key %v for type %v in new configuration", id, ctype)
206+
}
207+
208+
}
209+
}
210+
211+
return configMap, nil
212+
213+
}
214+
215+
// Validate attempts to validate a new configtx against the current config state
216+
func (cm *configurationManager) Validate(configtx *ab.ConfigurationEnvelope) error {
217+
cm.beginHandlers()
218+
_, err := cm.processConfig(configtx)
219+
cm.rollbackHandlers()
220+
return err
221+
}
222+
223+
// Apply attempts to apply a configtx to become the new configuration
224+
func (cm *configurationManager) Apply(configtx *ab.ConfigurationEnvelope) error {
225+
cm.beginHandlers()
226+
configMap, err := cm.processConfig(configtx)
227+
if err != nil {
228+
cm.rollbackHandlers()
229+
return err
230+
}
231+
cm.configuration = configMap
232+
cm.sequence = configtx.Sequence
233+
cm.commitHandlers()
234+
return nil
235+
}

0 commit comments

Comments
 (0)