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  }