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 }