config.go 16.4 KB
Newer Older
1
/*
2
Copyright IBM Corp. All Rights Reserved.
3

4
SPDX-License-Identifier: Apache-2.0
5
6
7
8
9
10
*/

package localconfig

import (
	"fmt"
11
	"path/filepath"
12
13
14
	"strings"
	"time"

15
	"github.com/hyperledger/fabric/common/flogging"
16
	"github.com/hyperledger/fabric/common/policies"
17
	"github.com/hyperledger/fabric/common/viperutil"
18
	cf "github.com/hyperledger/fabric/core/config"
19
	"github.com/hyperledger/fabric/msp"
20
21
	"github.com/hyperledger/fabric/protos/orderer/etcdraft"
	"github.com/spf13/viper"
22
23
)

24
25
26
27
28
const (
	// Prefix identifies the prefix for the configtxgen-related ENV vars.
	Prefix string = "CONFIGTX"
)

29
var logger = flogging.MustGetLogger("common.tools.configtxgen.localconfig")
30
var configName = strings.ToLower(Prefix)
31

32
const (
33
34
	// TestChainID is the channel name used for testing purposes when one is
	// not given
35
36
	TestChainID = "testchainid"

37
38
	// SampleInsecureSoloProfile references the sample profile which does not
	// include any MSPs and uses solo for ordering.
39
	SampleInsecureSoloProfile = "SampleInsecureSolo"
40
41
	// SampleDevModeSoloProfile references the sample profile which requires
	// only basic membership for admin privileges and uses solo for ordering.
42
	SampleDevModeSoloProfile = "SampleDevModeSolo"
43
44
	// SampleSingleMSPSoloProfile references the sample profile which includes
	// only the sample MSP and uses solo for ordering.
45
	SampleSingleMSPSoloProfile = "SampleSingleMSPSolo"
46

47
48
	// SampleInsecureKafkaProfile references the sample profile which does not
	// include any MSPs and uses Kafka for ordering.
49
	SampleInsecureKafkaProfile = "SampleInsecureKafka"
50
51
	// SampleDevModeKafkaProfile references the sample profile which requires only
	// basic membership for admin privileges and uses Kafka for ordering.
52
	SampleDevModeKafkaProfile = "SampleDevModeKafka"
53
54
	// SampleSingleMSPKafkaProfile references the sample profile which includes
	// only the sample MSP and uses Kafka for ordering.
55
56
	SampleSingleMSPKafkaProfile = "SampleSingleMSPKafka"

57
58
59
60
	// SampleDevModeEtcdRaftProfile references the sample profile used for testing
	// the etcd/raft-based ordering service.
	SampleDevModeEtcdRaftProfile = "SampleDevModeEtcdRaft"

61
62
	// SampleSingleMSPChannelProfile references the sample profile which
	// includes only the sample MSP and is used to create a channel
63
	SampleSingleMSPChannelProfile = "SampleSingleMSPChannel"
64

65
66
	// SampleConsortiumName is the sample consortium from the
	// sample configtx.yaml
67
	SampleConsortiumName = "SampleConsortium"
68
69
	// SampleOrgName is the name of the sample org in the sample profiles
	SampleOrgName = "SampleOrg"
70

71
72
	// AdminRoleAdminPrincipal is set as AdminRole to cause the MSP role of
	// type Admin to be used as the admin principal default
73
	AdminRoleAdminPrincipal = "Role.ADMIN"
74
75
	// MemberRoleAdminPrincipal is set as AdminRole to cause the MSP role of
	// type Member to be used as the admin principal default
76
	MemberRoleAdminPrincipal = "Role.MEMBER"
77
78
)

79
// TopLevel consists of the structs used by the configtxgen tool.
80
type TopLevel struct {
81
82
	Profiles      map[string]*Profile        `yaml:"Profiles"`
	Organizations []*Organization            `yaml:"Organizations"`
83
	Channel       *Profile                   `yaml:"Channel"`
84
85
86
	Application   *Application               `yaml:"Application"`
	Orderer       *Orderer                   `yaml:"Orderer"`
	Capabilities  map[string]map[string]bool `yaml:"Capabilities"`
87
	Resources     *Resources                 `yaml:"Resources"`
88
89
}

90
91
// Profile encodes orderer/application configuration combinations for the
// configtxgen tool.
92
type Profile struct {
93
94
95
96
97
	Consortium   string                 `yaml:"Consortium"`
	Application  *Application           `yaml:"Application"`
	Orderer      *Orderer               `yaml:"Orderer"`
	Consortiums  map[string]*Consortium `yaml:"Consortiums"`
	Capabilities map[string]bool        `yaml:"Capabilities"`
98
99
100
101
102
103
104
	Policies     map[string]*Policy     `yaml:"Policies"`
}

// Policy encodes a channel config policy
type Policy struct {
	Type string `yaml:"Type"`
	Rule string `yaml:"Rule"`
105
106
}

107
108
// Consortium represents a group of organizations which may create channels
// with each other
109
110
type Consortium struct {
	Organizations []*Organization `yaml:"Organizations"`
111
112
}

113
114
// Application encodes the application-level configuration needed in config
// transactions.
115
type Application struct {
116
117
118
119
	Organizations []*Organization    `yaml:"Organizations"`
	Capabilities  map[string]bool    `yaml:"Capabilities"`
	Resources     *Resources         `yaml:"Resources"`
	Policies      map[string]*Policy `yaml:"Policies"`
120
	ACLs          map[string]string  `yaml:"ACLs"`
121
122
}

123
124
// Resources encodes the application-level resources configuration needed to
// seed the resource tree
125
126
type Resources struct {
	DefaultModPolicy string
127
128
}

129
130
// Organization encodes the organization-level configuration needed in
// config transactions.
131
type Organization struct {
132
133
134
135
136
	Name     string             `yaml:"Name"`
	ID       string             `yaml:"ID"`
	MSPDir   string             `yaml:"MSPDir"`
	MSPType  string             `yaml:"MSPType"`
	Policies map[string]*Policy `yaml:"Policies"`
137

138
139
140
	// Note: Viper deserialization does not seem to care for
	// embedding of types, so we use one organization struct
	// for both orderers and applications.
141
	AnchorPeers []*AnchorPeer `yaml:"AnchorPeers"`
142
143
144
145
146

	// AdminPrincipal is deprecated and may be removed in a future release
	// it was used for modifying the default policy generation, but policies
	// may now be specified explicitly so it is redundant and unnecessary
	AdminPrincipal string `yaml:"AdminPrincipal"`
147
148
}

149
// AnchorPeer encodes the necessary fields to identify an anchor peer.
150
type AnchorPeer struct {
151
152
	Host string `yaml:"Host"`
	Port int    `yaml:"Port"`
153
154
}

155
156
// Orderer contains configuration which is used for the
// bootstrapping of an orderer by the provisional bootstrapper.
157
type Orderer struct {
158
159
160
161
162
163
164
165
166
167
	OrdererType   string                   `yaml:"OrdererType"`
	Addresses     []string                 `yaml:"Addresses"`
	BatchTimeout  time.Duration            `yaml:"BatchTimeout"`
	BatchSize     BatchSize                `yaml:"BatchSize"`
	Kafka         Kafka                    `yaml:"Kafka"`
	EtcdRaft      *etcdraft.ConfigMetadata `yaml:"EtcdRaft"`
	Organizations []*Organization          `yaml:"Organizations"`
	MaxChannels   uint64                   `yaml:"MaxChannels"`
	Capabilities  map[string]bool          `yaml:"Capabilities"`
	Policies      map[string]*Policy       `yaml:"Policies"`
168
169
}

170
// BatchSize contains configuration affecting the size of batches.
171
type BatchSize struct {
172
	MaxMessageCount   uint32 `yaml:"MaxMessageCount"`
173
174
	AbsoluteMaxBytes  uint32 `yaml:"AbsoluteMaxBytes"`
	PreferredMaxBytes uint32 `yaml:"PreferredMaxBytes"`
175
176
}

177
// Kafka contains configuration for the Kafka-based orderer.
178
type Kafka struct {
179
	Brokers []string `yaml:"Brokers"`
180
181
182
}

var genesisDefaults = TopLevel{
183
	Orderer: &Orderer{
184
185
		OrdererType:  "solo",
		Addresses:    []string{"127.0.0.1:7050"},
186
		BatchTimeout: 2 * time.Second,
187
		BatchSize: BatchSize{
188
			MaxMessageCount:   500,
189
			AbsoluteMaxBytes:  10 * 1024 * 1024,
190
			PreferredMaxBytes: 2 * 1024 * 1024,
191
192
193
194
		},
		Kafka: Kafka{
			Brokers: []string{"127.0.0.1:9092"},
		},
195
		EtcdRaft: &etcdraft.ConfigMetadata{
196
			Options: &etcdraft.Options{
Jay Guo's avatar
Jay Guo committed
197
198
199
200
201
				TickInterval:         "500ms",
				ElectionTick:         10,
				HeartbeatTick:        1,
				MaxInflightBlocks:    5,
				SnapshotIntervalSize: 20 * 1024 * 1024, // 20 MB
202
203
			},
		},
204
205
206
	},
}

207
// LoadTopLevel simply loads the configtx.yaml file into the structs above and
208
209
// completes their initialization. Config paths may optionally be provided and
// will be used in place of the FABRIC_CFG_PATH env variable.
210
211
212
//
// Note, for environment overrides to work properly within a profile, Load
// should be used instead.
213
func LoadTopLevel(configPaths ...string) *TopLevel {
214
	config := viper.New()
215
216
217
218
219
220
221
	if len(configPaths) > 0 {
		for _, p := range configPaths {
			config.AddConfigPath(p)
		}
		config.SetConfigName(configName)
	} else {
		cf.InitViper(config, configName)
222
	}
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249

	// For environment variables
	config.SetEnvPrefix(Prefix)
	config.AutomaticEnv()

	replacer := strings.NewReplacer(".", "_")
	config.SetEnvKeyReplacer(replacer)

	err := config.ReadInConfig()
	if err != nil {
		logger.Panic("Error reading configuration: ", err)
	}
	logger.Debugf("Using config file: %s", config.ConfigFileUsed())

	var uconf TopLevel
	err = viperutil.EnhancedExactUnmarshal(config, &uconf)
	if err != nil {
		logger.Panic("Error unmarshaling config into struct: ", err)
	}

	(&uconf).completeInitialization(filepath.Dir(config.ConfigFileUsed()))

	logger.Infof("Loaded configuration: %s", config.ConfigFileUsed())

	return &uconf
}

250
// Load returns the orderer/application config combination that corresponds to
251
252
253
// a given profile. Config paths may optionally be provided and will be used
// in place of the FABRIC_CFG_PATH env variable.
func Load(profile string, configPaths ...string) *Profile {
254
	config := viper.New()
255
256
257
258
259
260
261
	if len(configPaths) > 0 {
		for _, p := range configPaths {
			config.AddConfigPath(p)
		}
		config.SetConfigName(configName)
	} else {
		cf.InitViper(config, configName)
262
	}
263
264
265
266

	// For environment variables
	config.SetEnvPrefix(Prefix)
	config.AutomaticEnv()
267

268
269
	// This replacer allows substitution within the particular profile without
	// having to fully qualify the name
270
271
272
273
274
	replacer := strings.NewReplacer(strings.ToUpper(fmt.Sprintf("profiles.%s.", profile)), "", ".", "_")
	config.SetEnvKeyReplacer(replacer)

	err := config.ReadInConfig()
	if err != nil {
275
		logger.Panic("Error reading configuration: ", err)
276
	}
277
	logger.Debugf("Using config file: %s", config.ConfigFileUsed())
278
279
280
281

	var uconf TopLevel
	err = viperutil.EnhancedExactUnmarshal(config, &uconf)
	if err != nil {
282
		logger.Panic("Error unmarshaling config into struct: ", err)
283
284
285
286
	}

	result, ok := uconf.Profiles[profile]
	if !ok {
287
		logger.Panic("Could not find profile: ", profile)
288
289
290
291
	}

	result.completeInitialization(filepath.Dir(config.ConfigFileUsed()))

292
293
	logger.Infof("Loaded configuration: %s", config.ConfigFileUsed())

294
295
296
	return result
}

297
298
299
300
301
302
func (t *TopLevel) completeInitialization(configDir string) {
	for _, org := range t.Organizations {
		org.completeInitialization(configDir)
	}

	if t.Orderer != nil {
303
		t.Orderer.completeInitialization(configDir)
304
305
306
	}
}

307
func (p *Profile) completeInitialization(configDir string) {
308
309
	if p.Application != nil {
		for _, org := range p.Application.Organizations {
310
			org.completeInitialization(configDir)
311
		}
312
313
314
		if p.Application.Resources != nil {
			p.Application.Resources.completeInitialization()
		}
315
316
317
318
319
	}

	if p.Consortiums != nil {
		for _, consortium := range p.Consortiums {
			for _, org := range consortium.Organizations {
320
				org.completeInitialization(configDir)
321
322
323
324
			}
		}
	}

325
	if p.Orderer != nil {
326
327
328
329
330
		for _, org := range p.Orderer.Organizations {
			org.completeInitialization(configDir)
		}
		// Some profiles will not define orderer parameters
		p.Orderer.completeInitialization(configDir)
331
	}
332
333
}

334
335
336
337
338
339
340
341
342
343
344
func (r *Resources) completeInitialization() {
	for {
		switch {
		case r.DefaultModPolicy == "":
			r.DefaultModPolicy = policies.ChannelApplicationAdmins
		default:
			return
		}
	}
}

345
func (org *Organization) completeInitialization(configDir string) {
346
347
348
349
350
	// set the MSP type; if none is specified we assume BCCSP
	if org.MSPType == "" {
		org.MSPType = msp.ProviderTypeToString(msp.FABRIC)
	}

351
352
353
354
355
	if org.AdminPrincipal == "" {
		org.AdminPrincipal = AdminRoleAdminPrincipal
	}
	translatePaths(configDir, org)
}
356

357
358
func (ord *Orderer) completeInitialization(configDir string) {
loop:
359
360
	for {
		switch {
361
		case ord.OrdererType == "":
362
			logger.Infof("Orderer.OrdererType unset, setting to %v", genesisDefaults.Orderer.OrdererType)
363
364
			ord.OrdererType = genesisDefaults.Orderer.OrdererType
		case ord.Addresses == nil:
365
			logger.Infof("Orderer.Addresses unset, setting to %s", genesisDefaults.Orderer.Addresses)
366
367
			ord.Addresses = genesisDefaults.Orderer.Addresses
		case ord.BatchTimeout == 0:
368
			logger.Infof("Orderer.BatchTimeout unset, setting to %s", genesisDefaults.Orderer.BatchTimeout)
369
370
			ord.BatchTimeout = genesisDefaults.Orderer.BatchTimeout
		case ord.BatchSize.MaxMessageCount == 0:
371
			logger.Infof("Orderer.BatchSize.MaxMessageCount unset, setting to %v", genesisDefaults.Orderer.BatchSize.MaxMessageCount)
372
373
			ord.BatchSize.MaxMessageCount = genesisDefaults.Orderer.BatchSize.MaxMessageCount
		case ord.BatchSize.AbsoluteMaxBytes == 0:
374
			logger.Infof("Orderer.BatchSize.AbsoluteMaxBytes unset, setting to %v", genesisDefaults.Orderer.BatchSize.AbsoluteMaxBytes)
375
376
			ord.BatchSize.AbsoluteMaxBytes = genesisDefaults.Orderer.BatchSize.AbsoluteMaxBytes
		case ord.BatchSize.PreferredMaxBytes == 0:
377
			logger.Infof("Orderer.BatchSize.PreferredMaxBytes unset, setting to %v", genesisDefaults.Orderer.BatchSize.PreferredMaxBytes)
378
			ord.BatchSize.PreferredMaxBytes = genesisDefaults.Orderer.BatchSize.PreferredMaxBytes
379
		default:
380
381
382
383
			break loop
		}
	}

384
	logger.Infof("orderer type: %s", ord.OrdererType)
385
	// Additional, consensus type-dependent initialization goes here
386
	// Also using this to panic on unknown orderer type.
387
	switch ord.OrdererType {
388
389
	case "solo":
		// nothing to be done here
390
391
392
393
394
395
396
397
398
	case "kafka":
		if ord.Kafka.Brokers == nil {
			logger.Infof("Orderer.Kafka unset, setting to %v", genesisDefaults.Orderer.Kafka.Brokers)
			ord.Kafka.Brokers = genesisDefaults.Orderer.Kafka.Brokers
		}
	case etcdraft.TypeKey:
		if ord.EtcdRaft == nil {
			logger.Panicf("%s raft configuration missing", etcdraft.TypeKey)
		}
399
400
401
402
403
404
405
		if ord.EtcdRaft.Options == nil {
			logger.Infof("Orderer.EtcdRaft.Options unset, setting to %v", genesisDefaults.Orderer.EtcdRaft.Options)
			ord.EtcdRaft.Options = genesisDefaults.Orderer.EtcdRaft.Options
		}
	second_loop:
		for {
			switch {
406
			case ord.EtcdRaft.Options.TickInterval == "":
407
408
409
410
411
412
413
414
415
416
417
				logger.Infof("Orderer.EtcdRaft.Options.TickInterval unset, setting to %v", genesisDefaults.Orderer.EtcdRaft.Options.TickInterval)
				ord.EtcdRaft.Options.TickInterval = genesisDefaults.Orderer.EtcdRaft.Options.TickInterval

			case ord.EtcdRaft.Options.ElectionTick == 0:
				logger.Infof("Orderer.EtcdRaft.Options.ElectionTick unset, setting to %v", genesisDefaults.Orderer.EtcdRaft.Options.ElectionTick)
				ord.EtcdRaft.Options.ElectionTick = genesisDefaults.Orderer.EtcdRaft.Options.ElectionTick

			case ord.EtcdRaft.Options.HeartbeatTick == 0:
				logger.Infof("Orderer.EtcdRaft.Options.HeartbeatTick unset, setting to %v", genesisDefaults.Orderer.EtcdRaft.Options.HeartbeatTick)
				ord.EtcdRaft.Options.HeartbeatTick = genesisDefaults.Orderer.EtcdRaft.Options.HeartbeatTick

Jay Guo's avatar
Jay Guo committed
418
419
420
			case ord.EtcdRaft.Options.MaxInflightBlocks == 0:
				logger.Infof("Orderer.EtcdRaft.Options.MaxInflightBlocks unset, setting to %v", genesisDefaults.Orderer.EtcdRaft.Options.MaxInflightBlocks)
				ord.EtcdRaft.Options.MaxInflightBlocks = genesisDefaults.Orderer.EtcdRaft.Options.MaxInflightBlocks
421

Jay Guo's avatar
Jay Guo committed
422
423
424
			case ord.EtcdRaft.Options.SnapshotIntervalSize == 0:
				logger.Infof("Orderer.EtcdRaft.Options.SnapshotIntervalSize unset, setting to %v", genesisDefaults.Orderer.EtcdRaft.Options.SnapshotIntervalSize)
				ord.EtcdRaft.Options.SnapshotIntervalSize = genesisDefaults.Orderer.EtcdRaft.Options.SnapshotIntervalSize
Jay Guo's avatar
Jay Guo committed
425

426
427
428
429
430
431
432
433
			case len(ord.EtcdRaft.Consenters) == 0:
				logger.Panicf("%s configuration did not specify any consenter", etcdraft.TypeKey)

			default:
				break second_loop
			}
		}

434
435
436
437
		if _, err := time.ParseDuration(ord.EtcdRaft.Options.TickInterval); err != nil {
			logger.Panicf("Etcdraft TickInterval (%s) must be in time duration format", ord.EtcdRaft.Options.TickInterval)
		}

438
439
440
441
442
		// validate the specified members for Options
		if ord.EtcdRaft.Options.ElectionTick <= ord.EtcdRaft.Options.HeartbeatTick {
			logger.Panicf("election tick must be greater than heartbeat tick")
		}

443
		for _, c := range ord.EtcdRaft.GetConsenters() {
444
445
446
447
448
449
450
451
452
453
454
455
			if c.Host == "" {
				logger.Panicf("consenter info in %s configuration did not specify host", etcdraft.TypeKey)
			}
			if c.Port == 0 {
				logger.Panicf("consenter info in %s configuration did not specify port", etcdraft.TypeKey)
			}
			if c.ClientTlsCert == nil {
				logger.Panicf("consenter info in %s configuration did not specify client TLS cert", etcdraft.TypeKey)
			}
			if c.ServerTlsCert == nil {
				logger.Panicf("consenter info in %s configuration did not specify server TLS cert", etcdraft.TypeKey)
			}
456
457
458
459
460
461
			clientCertPath := string(c.GetClientTlsCert())
			cf.TranslatePathInPlace(configDir, &clientCertPath)
			c.ClientTlsCert = []byte(clientCertPath)
			serverCertPath := string(c.GetServerTlsCert())
			cf.TranslatePathInPlace(configDir, &serverCertPath)
			c.ServerTlsCert = []byte(serverCertPath)
462
		}
463
464
	default:
		logger.Panicf("unknown orderer type: %s", ord.OrdererType)
465
466
467
	}
}

468
469
470
func translatePaths(configDir string, org *Organization) {
	cf.TranslatePathInPlace(configDir, &org.MSPDir)
}