Skip to content

Commit 4db9abf

Browse files
author
Jason Yellick
committedOct 26, 2016
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 <jyellick@us.ibm.com>
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+
}
+499
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,499 @@
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+
"fmt"
21+
"testing"
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+
var defaultChain = []byte("DefaultChainID")
30+
31+
func defaultHandlers() map[ab.Configuration_ConfigurationType]Handler {
32+
handlers := make(map[ab.Configuration_ConfigurationType]Handler)
33+
for ctype := range ab.Configuration_ConfigurationType_name {
34+
handlers[ab.Configuration_ConfigurationType(ctype)] = NewBytesHandler()
35+
}
36+
return handlers
37+
}
38+
39+
// mockPolicy always returns the error set as policyResult
40+
type mockPolicy struct {
41+
policyResult error
42+
}
43+
44+
func (mp *mockPolicy) Evaluate(msg []byte, sigs []*ab.SignedData) error {
45+
if mp == nil {
46+
return fmt.Errorf("Invoked nil policy")
47+
}
48+
return mp.policyResult
49+
}
50+
51+
// mockPolicyManager always returns the policy set as policy, note that if unset, the default policy always returns error when evaluated
52+
type mockPolicyManager struct {
53+
policy *mockPolicy
54+
}
55+
56+
func (mpm *mockPolicyManager) GetPolicy(id string) (policies.Policy, bool) {
57+
return mpm.policy, (mpm.policy != nil)
58+
}
59+
60+
func makeConfiguration(id, modificationPolicy string, lastModified uint64, data []byte) *ab.Configuration {
61+
return &ab.Configuration{
62+
ChainID: defaultChain,
63+
ID: id,
64+
Data: data,
65+
ModificationPolicy: modificationPolicy,
66+
LastModified: lastModified,
67+
}
68+
}
69+
70+
func makeConfigurationEntry(id, modificationPolicy string, lastModified uint64, data []byte) *ab.ConfigurationEntry {
71+
config := makeConfiguration(id, modificationPolicy, lastModified, data)
72+
marshaledConfig, err := proto.Marshal(config)
73+
if err != nil {
74+
panic(err)
75+
}
76+
return &ab.ConfigurationEntry{
77+
Configuration: marshaledConfig,
78+
}
79+
}
80+
81+
// TestOmittedHandler tests that startup fails if not all configuration types have an associated handler
82+
func TestOmittedHandler(t *testing.T) {
83+
_, err := NewConfigurationManager(&ab.ConfigurationEnvelope{
84+
Sequence: 0,
85+
ChainID: defaultChain,
86+
}, &mockPolicyManager{&mockPolicy{}}, map[ab.Configuration_ConfigurationType]Handler{})
87+
88+
if err == nil {
89+
t.Fatalf("Should have failed to construct manager because handlers were missing")
90+
}
91+
}
92+
93+
// TestWrongChainID tests that a configuration update for a different chain ID fails
94+
func TestWrongChainID(t *testing.T) {
95+
cm, err := NewConfigurationManager(&ab.ConfigurationEnvelope{
96+
Sequence: 0,
97+
ChainID: defaultChain,
98+
}, &mockPolicyManager{&mockPolicy{}}, defaultHandlers())
99+
100+
if err != nil {
101+
t.Fatalf("Error constructing configuration manager: %s", err)
102+
}
103+
104+
newConfig := &ab.ConfigurationEnvelope{
105+
Sequence: 1,
106+
ChainID: []byte("wrongChain"),
107+
}
108+
109+
err = cm.Validate(newConfig)
110+
if err == nil {
111+
t.Errorf("Should have errored when validating a new configuration set the wrong chain ID")
112+
}
113+
114+
err = cm.Apply(newConfig)
115+
if err == nil {
116+
t.Errorf("Should have errored when applying a new configuration with the wrong chain ID")
117+
}
118+
}
119+
120+
// TestOldConfigReplay tests that resubmitting a config for a sequence number which is not newer is ignored
121+
func TestOldConfigReplay(t *testing.T) {
122+
cm, err := NewConfigurationManager(&ab.ConfigurationEnvelope{
123+
Sequence: 0,
124+
ChainID: defaultChain,
125+
}, &mockPolicyManager{&mockPolicy{}}, defaultHandlers())
126+
127+
if err != nil {
128+
t.Fatalf("Error constructing configuration manager: %s", err)
129+
}
130+
131+
newConfig := &ab.ConfigurationEnvelope{
132+
Sequence: 0,
133+
ChainID: defaultChain,
134+
}
135+
136+
err = cm.Validate(newConfig)
137+
if err == nil {
138+
t.Errorf("Should have errored when validating a configuration that is not a newer sequence number")
139+
}
140+
141+
err = cm.Apply(newConfig)
142+
if err == nil {
143+
t.Errorf("Should have errored when applying a configuration that is not a newer sequence number")
144+
}
145+
}
146+
147+
// TestInvalidInitialConfigByStructure tests to make sure that if the config contains corrupted configuration that construction results in error
148+
func TestInvalidInitialConfigByStructure(t *testing.T) {
149+
entries := []*ab.ConfigurationEntry{makeConfigurationEntry("foo", "foo", 0, []byte("foo"))}
150+
entries[0].Configuration = []byte("Corrupted")
151+
_, err := NewConfigurationManager(&ab.ConfigurationEnvelope{
152+
Sequence: 0,
153+
ChainID: defaultChain,
154+
Entries: entries,
155+
}, &mockPolicyManager{&mockPolicy{}}, defaultHandlers())
156+
157+
if err == nil {
158+
t.Fatalf("Should have failed to construct configuration by policy")
159+
}
160+
}
161+
162+
// TestValidConfigChange tests the happy path of updating a configuration value with no defaultModificationPolicy
163+
func TestValidConfigChange(t *testing.T) {
164+
cm, err := NewConfigurationManager(&ab.ConfigurationEnvelope{
165+
Sequence: 0,
166+
ChainID: defaultChain,
167+
}, &mockPolicyManager{}, defaultHandlers())
168+
169+
if err != nil {
170+
t.Fatalf("Error constructing configuration manager: %s", err)
171+
}
172+
173+
newConfig := &ab.ConfigurationEnvelope{
174+
Sequence: 1,
175+
ChainID: defaultChain,
176+
Entries: []*ab.ConfigurationEntry{makeConfigurationEntry("foo", "foo", 1, []byte("foo"))},
177+
}
178+
179+
err = cm.Validate(newConfig)
180+
if err != nil {
181+
t.Errorf("Should not have errored validating config: %s", err)
182+
}
183+
184+
err = cm.Apply(newConfig)
185+
if err != nil {
186+
t.Errorf("Should not have errored applying config: %s", err)
187+
}
188+
}
189+
190+
// TestConfigChangeNoUpdatedSequence tests that a new submitted config is rejected if it increments the
191+
// sequence number without a corresponding config item with that sequence number
192+
func TestConfigChangeNoUpdatedSequence(t *testing.T) {
193+
cm, err := NewConfigurationManager(&ab.ConfigurationEnvelope{
194+
Sequence: 0,
195+
ChainID: defaultChain,
196+
}, &mockPolicyManager{&mockPolicy{}}, defaultHandlers())
197+
198+
if err != nil {
199+
t.Fatalf("Error constructing configuration manager: %s", err)
200+
}
201+
202+
newConfig := &ab.ConfigurationEnvelope{
203+
Sequence: 1,
204+
ChainID: defaultChain,
205+
// Note that the entries do not contain any config items with seqNo=1, so this is invalid
206+
Entries: []*ab.ConfigurationEntry{makeConfigurationEntry("foo", "foo", 0, []byte("foo"))},
207+
}
208+
209+
err = cm.Validate(newConfig)
210+
if err == nil {
211+
t.Errorf("Should have errored validating config because no new sequence number matches")
212+
}
213+
214+
err = cm.Apply(newConfig)
215+
if err == nil {
216+
t.Errorf("Should have errored applying config because no new sequence number matches")
217+
}
218+
}
219+
220+
// TestConfigChangeRegressedSequence tests to make sure that a new config cannot roll back one of the
221+
// config values while advancing another
222+
func TestConfigChangeRegressedSequence(t *testing.T) {
223+
cm, err := NewConfigurationManager(&ab.ConfigurationEnvelope{
224+
Sequence: 1,
225+
ChainID: defaultChain,
226+
Entries: []*ab.ConfigurationEntry{makeConfigurationEntry("foo", "foo", 1, []byte("foo"))},
227+
}, &mockPolicyManager{&mockPolicy{}}, defaultHandlers())
228+
229+
if err != nil {
230+
t.Fatalf("Error constructing configuration manager: %s", err)
231+
}
232+
233+
newConfig := &ab.ConfigurationEnvelope{
234+
Sequence: 2,
235+
ChainID: defaultChain,
236+
Entries: []*ab.ConfigurationEntry{
237+
makeConfigurationEntry("foo", "foo", 0, []byte("foo")),
238+
makeConfigurationEntry("bar", "bar", 2, []byte("bar")),
239+
},
240+
}
241+
242+
err = cm.Validate(newConfig)
243+
if err == nil {
244+
t.Errorf("Should have errored validating config because foo's sequence number regressed")
245+
}
246+
247+
err = cm.Apply(newConfig)
248+
if err == nil {
249+
t.Errorf("Should have errored applying config because foo's sequence number regressed")
250+
}
251+
}
252+
253+
// TestConfigImplicitDelete tests to make sure that a new config does not implicitly delete config items
254+
// by omitting them in the new config
255+
func TestConfigImplicitDelete(t *testing.T) {
256+
cm, err := NewConfigurationManager(&ab.ConfigurationEnvelope{
257+
Sequence: 0,
258+
ChainID: defaultChain,
259+
Entries: []*ab.ConfigurationEntry{
260+
makeConfigurationEntry("foo", "foo", 0, []byte("foo")),
261+
makeConfigurationEntry("bar", "bar", 0, []byte("bar")),
262+
},
263+
}, &mockPolicyManager{&mockPolicy{}}, defaultHandlers())
264+
265+
if err != nil {
266+
t.Fatalf("Error constructing configuration manager: %s", err)
267+
}
268+
269+
newConfig := &ab.ConfigurationEnvelope{
270+
Sequence: 1,
271+
ChainID: defaultChain,
272+
Entries: []*ab.ConfigurationEntry{
273+
makeConfigurationEntry("bar", "bar", 1, []byte("bar")),
274+
},
275+
}
276+
277+
err = cm.Validate(newConfig)
278+
if err == nil {
279+
t.Errorf("Should have errored validating config because foo was implicitly deleted")
280+
}
281+
282+
err = cm.Apply(newConfig)
283+
if err == nil {
284+
t.Errorf("Should have errored applying config because foo was implicitly deleted")
285+
}
286+
}
287+
288+
// TestConfigModifyWithoutFullIncrease tests to make sure that if an item is modified in a config change
289+
// that it not only increments its LastModified, but also increments it to the current sequence number
290+
func TestConfigModifyWithoutFullIncrease(t *testing.T) {
291+
cm, err := NewConfigurationManager(&ab.ConfigurationEnvelope{
292+
Sequence: 0,
293+
ChainID: defaultChain,
294+
Entries: []*ab.ConfigurationEntry{
295+
makeConfigurationEntry("foo", "foo", 0, []byte("foo")),
296+
makeConfigurationEntry("bar", "bar", 0, []byte("bar")),
297+
},
298+
}, &mockPolicyManager{&mockPolicy{}}, defaultHandlers())
299+
300+
if err != nil {
301+
t.Fatalf("Error constructing configuration manager: %s", err)
302+
}
303+
304+
newConfig := &ab.ConfigurationEnvelope{
305+
Sequence: 1,
306+
ChainID: defaultChain,
307+
Entries: []*ab.ConfigurationEntry{
308+
makeConfigurationEntry("foo", "foo", 0, []byte("foo")),
309+
makeConfigurationEntry("bar", "bar", 1, []byte("bar")),
310+
},
311+
}
312+
313+
err = cm.Validate(newConfig)
314+
if err != nil {
315+
t.Errorf("Should not have errored validating config: %s", err)
316+
}
317+
318+
err = cm.Apply(newConfig)
319+
if err != nil {
320+
t.Errorf("Should not have errored applying config: %s", err)
321+
}
322+
323+
newConfig = &ab.ConfigurationEnvelope{
324+
Sequence: 2,
325+
ChainID: defaultChain,
326+
Entries: []*ab.ConfigurationEntry{
327+
makeConfigurationEntry("foo", "foo", 1, []byte("foo")),
328+
makeConfigurationEntry("bar", "bar", 2, []byte("bar")),
329+
},
330+
}
331+
332+
err = cm.Validate(newConfig)
333+
if err == nil {
334+
t.Errorf("Should have errored validating config because foo was modified, but its lastModifiedSeqNo did not increase to the current seqNo")
335+
}
336+
337+
err = cm.Apply(newConfig)
338+
if err == nil {
339+
t.Errorf("Should have errored applying config because foo was modified, but its lastModifiedSeqNo did not increase to the current seqNo")
340+
}
341+
}
342+
343+
// TestSilentConfigModification tests to make sure that even if a validly signed new configuration for an existing sequence number
344+
// is substituted into an otherwise valid new config, that the new config is rejected for attempting a modification without
345+
// increasing the config item's LastModified
346+
func TestSilentConfigModification(t *testing.T) {
347+
cm, err := NewConfigurationManager(&ab.ConfigurationEnvelope{
348+
Sequence: 0,
349+
ChainID: defaultChain,
350+
Entries: []*ab.ConfigurationEntry{
351+
makeConfigurationEntry("foo", "foo", 0, []byte("foo")),
352+
makeConfigurationEntry("bar", "bar", 0, []byte("bar")),
353+
},
354+
}, &mockPolicyManager{&mockPolicy{}}, defaultHandlers())
355+
356+
if err != nil {
357+
t.Fatalf("Error constructing configuration manager: %s", err)
358+
}
359+
360+
newConfig := &ab.ConfigurationEnvelope{
361+
Sequence: 1,
362+
ChainID: defaultChain,
363+
Entries: []*ab.ConfigurationEntry{
364+
makeConfigurationEntry("foo", "foo", 0, []byte("different")),
365+
makeConfigurationEntry("bar", "bar", 1, []byte("bar")),
366+
},
367+
}
368+
369+
err = cm.Validate(newConfig)
370+
if err == nil {
371+
t.Errorf("Should not errored validating config because foo was silently modified (despite modification allowed by policy)")
372+
}
373+
374+
err = cm.Apply(newConfig)
375+
if err == nil {
376+
t.Errorf("Should not errored applying config because foo was silently modified (despite modification allowed by policy)")
377+
}
378+
}
379+
380+
// TestInvalidInitialConfigByPolicy tests to make sure that if an existing policies does not validate the config that
381+
// even construction fails
382+
func TestInvalidInitialConfigByPolicy(t *testing.T) {
383+
_, err := NewConfigurationManager(&ab.ConfigurationEnvelope{
384+
Sequence: 0,
385+
ChainID: defaultChain,
386+
Entries: []*ab.ConfigurationEntry{makeConfigurationEntry("foo", "foo", 0, []byte("foo"))},
387+
}, &mockPolicyManager{&mockPolicy{fmt.Errorf("err")}}, defaultHandlers())
388+
// mockPolicyManager will return non-validating defualt policy
389+
390+
if err == nil {
391+
t.Fatalf("Should have failed to construct configuration by policy")
392+
}
393+
}
394+
395+
// TestConfigChangeViolatesPolicy checks to make sure that if policy rejects the validation of a config item that
396+
// it is rejected in a config update
397+
func TestConfigChangeViolatesPolicy(t *testing.T) {
398+
mpm := &mockPolicyManager{}
399+
cm, err := NewConfigurationManager(&ab.ConfigurationEnvelope{
400+
Sequence: 0,
401+
ChainID: defaultChain,
402+
}, mpm, defaultHandlers())
403+
404+
if err != nil {
405+
t.Fatalf("Error constructing configuration manager: %s", err)
406+
}
407+
// Set the mock policy to error
408+
mpm.policy = &mockPolicy{fmt.Errorf("err")}
409+
410+
newConfig := &ab.ConfigurationEnvelope{
411+
Sequence: 1,
412+
ChainID: defaultChain,
413+
Entries: []*ab.ConfigurationEntry{makeConfigurationEntry("foo", "foo", 1, []byte("foo"))},
414+
}
415+
416+
err = cm.Validate(newConfig)
417+
if err == nil {
418+
t.Errorf("Should have errored validating config because policy rejected modification")
419+
}
420+
421+
err = cm.Apply(newConfig)
422+
if err == nil {
423+
t.Errorf("Should have errored applying config because policy rejected modification")
424+
}
425+
}
426+
427+
type failHandler struct{}
428+
429+
func (fh failHandler) BeginConfig() {}
430+
func (fh failHandler) RollbackConfig() {}
431+
func (fh failHandler) CommitConfig() {}
432+
func (fh failHandler) ProposeConfig(item *ab.Configuration) error {
433+
return fmt.Errorf("Fail")
434+
}
435+
436+
// TestInvalidProposal checks that even if the policy allows the transaction and the sequence etc. is well formed,
437+
// that if the handler does not accept the config, it is rejected
438+
func TestInvalidProposal(t *testing.T) {
439+
handlers := defaultHandlers()
440+
handlers[ab.Configuration_ConfigurationType(0)] = failHandler{}
441+
cm, err := NewConfigurationManager(&ab.ConfigurationEnvelope{
442+
Sequence: 0,
443+
ChainID: defaultChain,
444+
}, &mockPolicyManager{&mockPolicy{}}, handlers)
445+
446+
if err != nil {
447+
t.Fatalf("Error constructing configuration manager: %s", err)
448+
}
449+
450+
newConfig := &ab.ConfigurationEnvelope{
451+
Sequence: 1,
452+
ChainID: defaultChain,
453+
Entries: []*ab.ConfigurationEntry{makeConfigurationEntry("foo", "foo", 1, []byte("foo"))},
454+
}
455+
456+
err = cm.Validate(newConfig)
457+
if err == nil {
458+
t.Errorf("Should have errored validating config because the handler rejected it")
459+
}
460+
461+
err = cm.Apply(newConfig)
462+
if err == nil {
463+
t.Errorf("Should have errored applying config because the handler rejected it")
464+
}
465+
}
466+
467+
// TestConfigItemOnWrongChain tests to make sure that a config is rejected if it contains an item for the wrong chain
468+
func TestConfigItemOnWrongChain(t *testing.T) {
469+
cm, err := NewConfigurationManager(&ab.ConfigurationEnvelope{
470+
Sequence: 0,
471+
ChainID: defaultChain,
472+
}, &mockPolicyManager{&mockPolicy{}}, defaultHandlers())
473+
474+
if err != nil {
475+
t.Fatalf("Error constructing configuration manager: %s", err)
476+
}
477+
478+
config := makeConfiguration("foo", "foo", 1, []byte("foo"))
479+
config.ChainID = []byte("Wrong")
480+
marshaledConfig, err := proto.Marshal(config)
481+
if err != nil {
482+
t.Fatalf("Should have been able marshal config: %s", err)
483+
}
484+
newConfig := &ab.ConfigurationEnvelope{
485+
Sequence: 1,
486+
ChainID: defaultChain,
487+
Entries: []*ab.ConfigurationEntry{&ab.ConfigurationEntry{Configuration: marshaledConfig}},
488+
}
489+
490+
err = cm.Validate(newConfig)
491+
if err == nil {
492+
t.Errorf("Should have errored validating config because new config item is for a different chain")
493+
}
494+
495+
err = cm.Apply(newConfig)
496+
if err == nil {
497+
t.Errorf("Should have errored applying config because new config item is for a different chain")
498+
}
499+
}

0 commit comments

Comments
 (0)
Please sign in to comment.