github.com/kisexp/xdchain@v0.0.0-20211206025815-490d6b732aa7/eth/quorum_protocol.go (about)

     1  package eth
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  
     7  	"github.com/kisexp/xdchain/crypto"
     8  	"github.com/kisexp/xdchain/log"
     9  
    10  	"github.com/kisexp/xdchain/consensus"
    11  	"github.com/kisexp/xdchain/p2p"
    12  	"github.com/kisexp/xdchain/p2p/enode"
    13  )
    14  
    15  // Quorum: quorum_protocol enables the eth service to return two different protocols, one for the eth mainnet "eth" service,
    16  //         and one for the quorum specific consensus algo, obtained from engine.consensus
    17  //         2021 Jan in the future consensus (istanbul) may run from its own service and use a single subprotocol there,
    18  //         instead of overloading the eth service.
    19  
    20  var (
    21  	// errEthPeerNil is returned when no eth peer is found to be associated with a p2p peer.
    22  	errEthPeerNil           = errors.New("eth peer was nil")
    23  	errEthPeerNotRegistered = errors.New("eth peer was not registered")
    24  )
    25  
    26  // quorum consensus Protocol variables are optionally set in addition to the "eth" protocol variables (eth/protocol.go).
    27  var quorumConsensusProtocolName = ""
    28  
    29  // ProtocolVersions are the supported versions of the quorum consensus protocol (first is primary), e.g. []uint{Istanbul64, Istanbul99, Istanbul100}.
    30  var quorumConsensusProtocolVersions []uint
    31  
    32  // protocol Length describe the number of messages support by the protocol/version map[uint]uint64{Istanbul64: 18, Istanbul99: 18, Istanbul100: 18}
    33  var quorumConsensusProtocolLengths map[uint]uint64
    34  
    35  // makeQuorumConsensusProtocol is similar to eth/handler.go -> makeProtocol. Called from eth/handler.go -> Protocols.
    36  // returns the supported subprotocol to the p2p server.
    37  // The Run method starts the protocol and is called by the p2p server. The quorum consensus subprotocol,
    38  // leverages the peer created and managed by the "eth" subprotocol.
    39  // The quorum consensus protocol requires that the "eth" protocol is running as well.
    40  func (pm *ProtocolManager) makeQuorumConsensusProtocol(ProtoName string, version uint, length uint64) p2p.Protocol {
    41  
    42  	return p2p.Protocol{
    43  		Name:    ProtoName,
    44  		Version: version,
    45  		Length:  length,
    46  		// no new peer created, uses the "eth" peer, so no peer management needed.
    47  		Run: func(p *p2p.Peer, rw p2p.MsgReadWriter) error {
    48  			/*
    49  			* 1. wait for the eth protocol to create and register an eth peer.
    50  			* 2. get the associate eth peer that was registered by he "eth" protocol.
    51  			* 2. add the rw protocol for the quorum subprotocol to the eth peer.
    52  			* 3. start listening for incoming messages.
    53  			* 4. the incoming message will be sent on the quorum specific subprotocol, e.g. "istanbul/100".
    54  			* 5. send messages to the consensus engine handler.
    55  			* 7. messages to other to other peers listening to the subprotocol can be sent using the
    56  			*    (eth)peer.ConsensusSend() which will write to the protoRW.
    57  			 */
    58  			// wait for the "eth" protocol to create and register the peer (added to peerset)
    59  			select {
    60  			case <-p.EthPeerRegistered:
    61  				// the ethpeer should be registered, try to retrieve it and start the consensus handler.
    62  				p2pPeerId := fmt.Sprintf("%x", p.ID().Bytes()[:8])
    63  				ethPeer := pm.peers.Peer(p2pPeerId)
    64  				if ethPeer != nil {
    65  					p.Log().Debug("consensus subprotocol retrieved eth peer from peerset", "ethPeer.id", ethPeer.id, "ProtoName", ProtoName)
    66  					// add the rw protocol for the quorum subprotocol to the eth peer.
    67  					ethPeer.addConsensusProtoRW(rw)
    68  					return pm.handleConsensusLoop(p, rw)
    69  				}
    70  				p.Log().Error("consensus subprotocol retrieved nil eth peer from peerset", "ethPeer.id", ethPeer)
    71  				return errEthPeerNil
    72  			case <-p.EthPeerDisconnected:
    73  				return errEthPeerNotRegistered
    74  			}
    75  		},
    76  		NodeInfo: func() interface{} {
    77  			return pm.NodeInfo()
    78  		},
    79  		PeerInfo: func(id enode.ID) interface{} {
    80  			if p := pm.peers.Peer(fmt.Sprintf("%x", id[:8])); p != nil {
    81  				return p.Info()
    82  			}
    83  			return nil
    84  		},
    85  	}
    86  }
    87  
    88  func (pm *ProtocolManager) handleConsensusLoop(p *p2p.Peer, protoRW p2p.MsgReadWriter) error {
    89  	// Handle incoming messages until the connection is torn down
    90  	for {
    91  		if err := pm.handleConsensus(p, protoRW); err != nil {
    92  			p.Log().Debug("Ethereum quorum message handling failed", "err", err)
    93  			return err
    94  		}
    95  	}
    96  }
    97  
    98  // This is a no-op because the eth handleMsg main loop handle ibf message as well.
    99  func (pm *ProtocolManager) handleConsensus(p *p2p.Peer, protoRW p2p.MsgReadWriter) error {
   100  	// Read the next message from the remote peer (in protoRW), and ensure it's fully consumed
   101  	msg, err := protoRW.ReadMsg()
   102  	if err != nil {
   103  		return err
   104  	}
   105  	if msg.Size > protocolMaxMsgSize {
   106  		return errResp(ErrMsgTooLarge, "%v > %v", msg.Size, protocolMaxMsgSize)
   107  	}
   108  	defer msg.Discard()
   109  
   110  	// See if the consensus engine protocol can handle this message, e.g. istanbul will check for message is
   111  	// istanbulMsg = 0x11, and NewBlockMsg = 0x07.
   112  	handled, err := pm.handleConsensusMsg(p, msg)
   113  	if handled {
   114  		p.Log().Debug("consensus message was handled by consensus engine", "handled", handled,
   115  			"quorumConsensusProtocolName", quorumConsensusProtocolName, "err", err)
   116  		return err
   117  	}
   118  
   119  	return nil
   120  }
   121  
   122  func (pm *ProtocolManager) handleConsensusMsg(p *p2p.Peer, msg p2p.Msg) (bool, error) {
   123  	if handler, ok := pm.engine.(consensus.Handler); ok {
   124  		pubKey := p.Node().Pubkey()
   125  		addr := crypto.PubkeyToAddress(*pubKey)
   126  		handled, err := handler.HandleMsg(addr, msg)
   127  		return handled, err
   128  	}
   129  	return false, nil
   130  }
   131  
   132  // makeLegacyProtocol is basically a copy of the eth makeProtocol, but for legacy subprotocols, e.g. "istanbul/99" "istabnul/64"
   133  // If support legacy subprotocols is removed, remove this and associated code as well.
   134  // If quorum is using a legacy protocol then the "eth" subprotocol should not be available.
   135  func (pm *ProtocolManager) makeLegacyProtocol(protoName string, version uint, length uint64) p2p.Protocol {
   136  	log.Debug("registering a legacy protocol ", "protoName", protoName)
   137  	return p2p.Protocol{
   138  		Name:    protoName,
   139  		Version: version,
   140  		Length:  length,
   141  		Run: func(p *p2p.Peer, rw p2p.MsgReadWriter) error {
   142  			peer := pm.newPeer(int(version), p, rw, pm.txpool.Get)
   143  			peer.addConsensusProtoRW(rw)
   144  			return pm.runPeer(peer, protoName)
   145  		},
   146  		NodeInfo: func() interface{} {
   147  			return pm.NodeInfo()
   148  		},
   149  		PeerInfo: func(id enode.ID) interface{} {
   150  			if p := pm.peers.Peer(fmt.Sprintf("%x", id[:8])); p != nil {
   151  				return p.Info()
   152  			}
   153  			return nil
   154  		},
   155  	}
   156  }
   157  
   158  func (s *Ethereum) quorumConsensusProtocols() []p2p.Protocol {
   159  	protos := make([]p2p.Protocol, len(quorumConsensusProtocolVersions))
   160  	for i, vsn := range quorumConsensusProtocolVersions {
   161  		// if we have a legacy protocol, e.g. istanbul/99, istanbul/64 then the protocol handler is will be the "eth"
   162  		// protocol handler, and the subprotocol "eth" will not be used, but rather the legacy subprotocol will handle
   163  		// both eth messages and consensus messages.
   164  		if isLegacyProtocol(quorumConsensusProtocolName, vsn) {
   165  			length, ok := quorumConsensusProtocolLengths[vsn]
   166  			if !ok {
   167  				panic("makeProtocol for unknown version")
   168  			}
   169  			lp := s.protocolManager.makeLegacyProtocol(quorumConsensusProtocolName, vsn, length)
   170  			protos[i] = lp
   171  		} else {
   172  			length, ok := quorumConsensusProtocolLengths[vsn]
   173  			if !ok {
   174  				panic("makeQuorumConsensusProtocol for unknown version")
   175  			}
   176  			protos[i] = s.protocolManager.makeQuorumConsensusProtocol(quorumConsensusProtocolName, vsn, length)
   177  		}
   178  	}
   179  	return protos
   180  }
   181  
   182  // istanbul/64, istanbul/99, clique/63, clique/64 all override the "eth" subprotocol.
   183  func isLegacyProtocol(name string, version uint) bool {
   184  	// protocols that override "eth" subprotocol and run only the quorum subprotocol.
   185  	quorumLegacyProtocols := map[string][]uint{"istanbul": {64, 99}, "clique": {63, 64}}
   186  	for lpName, lpVersions := range quorumLegacyProtocols {
   187  		if lpName == name {
   188  			for _, v := range lpVersions {
   189  				if v == version {
   190  					return true
   191  				}
   192  			}
   193  		}
   194  	}
   195  	return false
   196  }
   197  
   198  // Used to send consensus subprotocol messages from an "eth" peer, e.g.  "istanbul/100" subprotocol messages.
   199  func (p *peer) SendConsensus(msgcode uint64, data interface{}) error {
   200  	if p.consensusRw == nil {
   201  		return nil
   202  	}
   203  	return p2p.Send(p.consensusRw, msgcode, data)
   204  }
   205  
   206  // SendQBFTConsensus is used to send consensus subprotocol messages from an "eth" peer without encoding the payload
   207  func (p *peer) SendQBFTConsensus(msgcode uint64, payload []byte) error {
   208  	if p.consensusRw == nil {
   209  		return nil
   210  	}
   211  	return p2p.SendWithNoEncoding(p.consensusRw, msgcode, payload)
   212  }
   213  
   214  func (p *peer) addConsensusProtoRW(rw p2p.MsgReadWriter) *peer {
   215  	p.consensusRw = rw
   216  	return p
   217  }