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