github.com/hechain20/hechain@v0.0.0-20220316014945-b544036ba106/orderer/consensus/etcdraft/consenter.go (about) 1 /* 2 Copyright hechain. All Rights Reserved. 3 4 SPDX-License-Identifier: Apache-2.0 5 */ 6 7 package etcdraft 8 9 import ( 10 "bytes" 11 "path" 12 "reflect" 13 "time" 14 15 "code.cloudfoundry.org/clock" 16 "github.com/golang/protobuf/proto" 17 "github.com/hechain20/hechain/bccsp" 18 "github.com/hechain20/hechain/common/channelconfig" 19 "github.com/hechain20/hechain/common/crypto" 20 "github.com/hechain20/hechain/common/flogging" 21 "github.com/hechain20/hechain/common/metrics" 22 "github.com/hechain20/hechain/internal/pkg/comm" 23 "github.com/hechain20/hechain/orderer/common/cluster" 24 "github.com/hechain20/hechain/orderer/common/localconfig" 25 "github.com/hechain20/hechain/orderer/common/types" 26 "github.com/hechain20/hechain/orderer/consensus" 27 "github.com/hechain20/hechain/orderer/consensus/inactive" 28 "github.com/hechain20/hechain/protoutil" 29 "github.com/hyperledger/fabric-protos-go/common" 30 "github.com/hyperledger/fabric-protos-go/orderer" 31 "github.com/hyperledger/fabric-protos-go/orderer/etcdraft" 32 "github.com/mitchellh/mapstructure" 33 "github.com/pkg/errors" 34 "go.etcd.io/etcd/raft" 35 ) 36 37 //go:generate counterfeiter -o mocks/inactive_chain_registry.go --fake-name InactiveChainRegistry . InactiveChainRegistry 38 39 // InactiveChainRegistry registers chains that are inactive 40 type InactiveChainRegistry interface { 41 // TrackChain tracks a chain with the given name, and calls the given callback 42 // when this chain should be created. 43 TrackChain(chainName string, genesisBlock *common.Block, createChain func()) 44 // Stop stops the InactiveChainRegistry. This is used when removing the 45 // system channel. 46 Stop() 47 } 48 49 //go:generate counterfeiter -o mocks/chain_manager.go --fake-name ChainManager . ChainManager 50 51 // ChainManager defines the methods from multichannel.Registrar needed by the Consenter. 52 type ChainManager interface { 53 GetConsensusChain(channelID string) consensus.Chain 54 CreateChain(channelID string) 55 SwitchChainToFollower(channelID string) 56 ReportConsensusRelationAndStatusMetrics(channelID string, relation types.ConsensusRelation, status types.Status) 57 } 58 59 // Config contains etcdraft configurations 60 type Config struct { 61 WALDir string // WAL data of <my-channel> is stored in WALDir/<my-channel> 62 SnapDir string // Snapshots of <my-channel> are stored in SnapDir/<my-channel> 63 EvictionSuspicion string // Duration threshold that the node samples in order to suspect its eviction from the channel. 64 TickIntervalOverride string // Duration to use for tick interval instead of what is specified in the channel config. 65 } 66 67 // Consenter implements etcdraft consenter 68 type Consenter struct { 69 ChainManager ChainManager 70 InactiveChainRegistry InactiveChainRegistry 71 Dialer *cluster.PredicateDialer 72 Communication cluster.Communicator 73 *Dispatcher 74 Logger *flogging.FabricLogger 75 EtcdRaftConfig Config 76 OrdererConfig localconfig.TopLevel 77 Cert []byte 78 Metrics *Metrics 79 BCCSP bccsp.BCCSP 80 } 81 82 // TargetChannel extracts the channel from the given proto.Message. 83 // Returns an empty string on failure. 84 func (c *Consenter) TargetChannel(message proto.Message) string { 85 switch req := message.(type) { 86 case *orderer.ConsensusRequest: 87 return req.Channel 88 case *orderer.SubmitRequest: 89 return req.Channel 90 default: 91 return "" 92 } 93 } 94 95 // ReceiverByChain returns the MessageReceiver for the given channelID or nil 96 // if not found. 97 func (c *Consenter) ReceiverByChain(channelID string) MessageReceiver { 98 chain := c.ChainManager.GetConsensusChain(channelID) 99 if chain == nil { 100 return nil 101 } 102 if etcdRaftChain, isEtcdRaftChain := chain.(*Chain); isEtcdRaftChain { 103 return etcdRaftChain 104 } 105 c.Logger.Warningf("Chain %s is of type %v and not etcdraft.Chain", channelID, reflect.TypeOf(chain)) 106 return nil 107 } 108 109 func (c *Consenter) detectSelfID(consenters map[uint64]*etcdraft.Consenter) (uint64, error) { 110 thisNodeCertAsDER, err := pemToDER(c.Cert, 0, "server", c.Logger) 111 if err != nil { 112 return 0, err 113 } 114 115 var serverCertificates []string 116 for nodeID, cst := range consenters { 117 serverCertificates = append(serverCertificates, string(cst.ServerTlsCert)) 118 119 certAsDER, err := pemToDER(cst.ServerTlsCert, nodeID, "server", c.Logger) 120 if err != nil { 121 return 0, err 122 } 123 124 if crypto.CertificatesWithSamePublicKey(thisNodeCertAsDER, certAsDER) == nil { 125 return nodeID, nil 126 } 127 } 128 129 c.Logger.Warning("Could not find", string(c.Cert), "among", serverCertificates) 130 return 0, cluster.ErrNotInChannel 131 } 132 133 // HandleChain returns a new Chain instance or an error upon failure 134 func (c *Consenter) HandleChain(support consensus.ConsenterSupport, metadata *common.Metadata) (consensus.Chain, error) { 135 m := &etcdraft.ConfigMetadata{} 136 if err := proto.Unmarshal(support.SharedConfig().ConsensusMetadata(), m); err != nil { 137 return nil, errors.Wrap(err, "failed to unmarshal consensus metadata") 138 } 139 140 if m.Options == nil { 141 return nil, errors.New("etcdraft options have not been provided") 142 } 143 144 isMigration := (metadata == nil || len(metadata.Value) == 0) && (support.Height() > 1) 145 if isMigration { 146 c.Logger.Debugf("Block metadata is nil at block height=%d, it is consensus-type migration", support.Height()) 147 } 148 149 // determine raft replica set mapping for each node to its id 150 // for newly started chain we need to read and initialize raft 151 // metadata by creating mapping between conseter and its id. 152 // In case chain has been restarted we restore raft metadata 153 // information from the recently committed block meta data 154 // field. 155 blockMetadata, err := ReadBlockMetadata(metadata, m) 156 if err != nil { 157 return nil, errors.Wrapf(err, "failed to read Raft metadata") 158 } 159 160 consenters := CreateConsentersMap(blockMetadata, m) 161 162 id, err := c.detectSelfID(consenters) 163 if err != nil { 164 if c.InactiveChainRegistry != nil { 165 // There is a system channel, use the InactiveChainRegistry to track the 166 // future config updates of application channel. 167 c.InactiveChainRegistry.TrackChain(support.ChannelID(), support.Block(0), func() { 168 c.ChainManager.CreateChain(support.ChannelID()) 169 }) 170 c.ChainManager.ReportConsensusRelationAndStatusMetrics(support.ChannelID(), types.ConsensusRelationConfigTracker, types.StatusInactive) 171 return &inactive.Chain{Err: errors.Errorf("channel %s is not serviced by me", support.ChannelID())}, nil 172 } 173 174 return nil, errors.Wrap(err, "without a system channel, a follower should have been created") 175 } 176 177 var evictionSuspicion time.Duration 178 if c.EtcdRaftConfig.EvictionSuspicion == "" { 179 c.Logger.Infof("EvictionSuspicion not set, defaulting to %v", DefaultEvictionSuspicion) 180 evictionSuspicion = DefaultEvictionSuspicion 181 } else { 182 evictionSuspicion, err = time.ParseDuration(c.EtcdRaftConfig.EvictionSuspicion) 183 if err != nil { 184 c.Logger.Panicf("Failed parsing Consensus.EvictionSuspicion: %s: %v", c.EtcdRaftConfig.EvictionSuspicion, err) 185 } 186 } 187 188 var tickInterval time.Duration 189 if c.EtcdRaftConfig.TickIntervalOverride == "" { 190 tickInterval, err = time.ParseDuration(m.Options.TickInterval) 191 if err != nil { 192 return nil, errors.Errorf("failed to parse TickInterval (%s) to time duration", m.Options.TickInterval) 193 } 194 } else { 195 tickInterval, err = time.ParseDuration(c.EtcdRaftConfig.TickIntervalOverride) 196 if err != nil { 197 return nil, errors.WithMessage(err, "failed parsing Consensus.TickIntervalOverride") 198 } 199 c.Logger.Infof("TickIntervalOverride is set, overriding channel configuration tick interval to %v", tickInterval) 200 } 201 202 opts := Options{ 203 RPCTimeout: c.OrdererConfig.General.Cluster.RPCTimeout, 204 RaftID: id, 205 Clock: clock.NewClock(), 206 MemoryStorage: raft.NewMemoryStorage(), 207 Logger: c.Logger, 208 209 TickInterval: tickInterval, 210 ElectionTick: int(m.Options.ElectionTick), 211 HeartbeatTick: int(m.Options.HeartbeatTick), 212 MaxInflightBlocks: int(m.Options.MaxInflightBlocks), 213 MaxSizePerMsg: uint64(support.SharedConfig().BatchSize().PreferredMaxBytes), 214 SnapshotIntervalSize: m.Options.SnapshotIntervalSize, 215 216 BlockMetadata: blockMetadata, 217 Consenters: consenters, 218 219 MigrationInit: isMigration, 220 221 WALDir: path.Join(c.EtcdRaftConfig.WALDir, support.ChannelID()), 222 SnapDir: path.Join(c.EtcdRaftConfig.SnapDir, support.ChannelID()), 223 EvictionSuspicion: evictionSuspicion, 224 Cert: c.Cert, 225 Metrics: c.Metrics, 226 } 227 228 rpc := &cluster.RPC{ 229 Timeout: c.OrdererConfig.General.Cluster.RPCTimeout, 230 Logger: c.Logger, 231 Channel: support.ChannelID(), 232 Comm: c.Communication, 233 StreamsByType: cluster.NewStreamsByType(), 234 } 235 236 var haltCallback func() // called after the etcdraft.Chain halts when it detects eviction form the cluster. 237 if c.InactiveChainRegistry != nil { 238 // when we have a system channel, we use the InactiveChainRegistry to track membership upon eviction. 239 c.Logger.Info("With system channel: after eviction InactiveChainRegistry.TrackChain will be called") 240 haltCallback = func() { 241 c.InactiveChainRegistry.TrackChain(support.ChannelID(), nil, func() { c.ChainManager.CreateChain(support.ChannelID()) }) 242 c.ChainManager.ReportConsensusRelationAndStatusMetrics(support.ChannelID(), types.ConsensusRelationConfigTracker, types.StatusInactive) 243 } 244 } else { 245 // when we do NOT have a system channel, we switch to a follower.Chain upon eviction. 246 c.Logger.Info("Without system channel: after eviction Registrar.SwitchToFollower will be called") 247 haltCallback = func() { c.ChainManager.SwitchChainToFollower(support.ChannelID()) } 248 } 249 250 return NewChain( 251 support, 252 opts, 253 c.Communication, 254 rpc, 255 c.BCCSP, 256 func() (BlockPuller, error) { 257 return NewBlockPuller(support, c.Dialer, c.OrdererConfig.General.Cluster, c.BCCSP) 258 }, 259 haltCallback, 260 nil, 261 ) 262 } 263 264 func (c *Consenter) IsChannelMember(joinBlock *common.Block) (bool, error) { 265 if joinBlock == nil { 266 return false, errors.New("nil block") 267 } 268 envelopeConfig, err := protoutil.ExtractEnvelope(joinBlock, 0) 269 if err != nil { 270 return false, err 271 } 272 bundle, err := channelconfig.NewBundleFromEnvelope(envelopeConfig, c.BCCSP) 273 if err != nil { 274 return false, err 275 } 276 oc, exists := bundle.OrdererConfig() 277 if !exists { 278 return false, errors.New("no orderer config in bundle") 279 } 280 configMetadata := &etcdraft.ConfigMetadata{} 281 if err := proto.Unmarshal(oc.ConsensusMetadata(), configMetadata); err != nil { 282 return false, err 283 } 284 285 verifyOpts, err := createX509VerifyOptions(oc) 286 if err != nil { 287 return false, errors.Wrapf(err, "failed to create x509 verify options from orderer config") 288 } 289 290 if err := VerifyConfigMetadata(configMetadata, verifyOpts); err != nil { 291 return false, errors.Wrapf(err, "failed to validate config metadata of ordering config") 292 } 293 294 member := false 295 for _, consenter := range configMetadata.Consenters { 296 if bytes.Equal(c.Cert, consenter.ServerTlsCert) || bytes.Equal(c.Cert, consenter.ClientTlsCert) { 297 member = true 298 break 299 } 300 } 301 302 return member, nil 303 } 304 305 // RemoveInactiveChainRegistry stops and removes the inactive chain registry. 306 // This is used when removing the system channel. 307 func (c *Consenter) RemoveInactiveChainRegistry() { 308 if c.InactiveChainRegistry == nil { 309 return 310 } 311 c.InactiveChainRegistry.Stop() 312 c.InactiveChainRegistry = nil 313 } 314 315 // ReadBlockMetadata attempts to read raft metadata from block metadata, if available. 316 // otherwise, it reads raft metadata from config metadata supplied. 317 func ReadBlockMetadata(blockMetadata *common.Metadata, configMetadata *etcdraft.ConfigMetadata) (*etcdraft.BlockMetadata, error) { 318 if blockMetadata != nil && len(blockMetadata.Value) != 0 { // we have consenters mapping from block 319 m := &etcdraft.BlockMetadata{} 320 if err := proto.Unmarshal(blockMetadata.Value, m); err != nil { 321 return nil, errors.Wrap(err, "failed to unmarshal block's metadata") 322 } 323 return m, nil 324 } 325 326 m := &etcdraft.BlockMetadata{ 327 NextConsenterId: 1, 328 ConsenterIds: make([]uint64, len(configMetadata.Consenters)), 329 } 330 // need to read consenters from the configuration 331 for i := range m.ConsenterIds { 332 m.ConsenterIds[i] = m.NextConsenterId 333 m.NextConsenterId++ 334 } 335 336 return m, nil 337 } 338 339 // New creates a etcdraft Consenter 340 func New( 341 clusterDialer *cluster.PredicateDialer, 342 conf *localconfig.TopLevel, 343 srvConf comm.ServerConfig, 344 srv *comm.GRPCServer, 345 registrar ChainManager, 346 icr InactiveChainRegistry, 347 metricsProvider metrics.Provider, 348 bccsp bccsp.BCCSP, 349 ) *Consenter { 350 logger := flogging.MustGetLogger("orderer.consensus.etcdraft") 351 352 var cfg Config 353 err := mapstructure.Decode(conf.Consensus, &cfg) 354 if err != nil { 355 logger.Panicf("Failed to decode etcdraft configuration: %s", err) 356 } 357 358 consenter := &Consenter{ 359 ChainManager: registrar, 360 Cert: srvConf.SecOpts.Certificate, 361 Logger: logger, 362 EtcdRaftConfig: cfg, 363 OrdererConfig: *conf, 364 Dialer: clusterDialer, 365 Metrics: NewMetrics(metricsProvider), 366 InactiveChainRegistry: icr, 367 BCCSP: bccsp, 368 } 369 consenter.Dispatcher = &Dispatcher{ 370 Logger: logger, 371 ChainSelector: consenter, 372 } 373 374 comm := createComm(clusterDialer, consenter, conf.General.Cluster, metricsProvider) 375 consenter.Communication = comm 376 svc := &cluster.Service{ 377 CertExpWarningThreshold: conf.General.Cluster.CertExpirationWarningThreshold, 378 MinimumExpirationWarningInterval: cluster.MinimumExpirationWarningInterval, 379 StreamCountReporter: &cluster.StreamCountReporter{ 380 Metrics: comm.Metrics, 381 }, 382 StepLogger: flogging.MustGetLogger("orderer.common.cluster.step"), 383 Logger: flogging.MustGetLogger("orderer.common.cluster"), 384 Dispatcher: comm, 385 } 386 orderer.RegisterClusterServer(srv.Server(), svc) 387 388 if icr == nil { 389 logger.Debug("Created an etcdraft consenter without a system channel, InactiveChainRegistry is nil") 390 } 391 392 return consenter 393 } 394 395 func createComm(clusterDialer *cluster.PredicateDialer, c *Consenter, config localconfig.Cluster, p metrics.Provider) *cluster.Comm { 396 metrics := cluster.NewMetrics(p) 397 logger := flogging.MustGetLogger("orderer.common.cluster") 398 399 compareCert := cluster.CachePublicKeyComparisons(func(a, b []byte) bool { 400 err := crypto.CertificatesWithSamePublicKey(a, b) 401 if err != nil && err != crypto.ErrPubKeyMismatch { 402 crypto.LogNonPubKeyMismatchErr(logger.Errorf, err, a, b) 403 } 404 return err == nil 405 }) 406 407 comm := &cluster.Comm{ 408 MinimumExpirationWarningInterval: cluster.MinimumExpirationWarningInterval, 409 CertExpWarningThreshold: config.CertExpirationWarningThreshold, 410 SendBufferSize: config.SendBufferSize, 411 Logger: logger, 412 Chan2Members: make(map[string]cluster.MemberMapping), 413 Connections: cluster.NewConnectionStore(clusterDialer, metrics.EgressTLSConnectionCount), 414 Metrics: metrics, 415 ChanExt: c, 416 H: c, 417 CompareCertificate: compareCert, 418 } 419 c.Communication = comm 420 return comm 421 }