github.com/shyftnetwork/go-empyrean@v1.8.3-0.20191127201940-fbfca9338f04/swarm/pss/client/client.go (about) 1 // Copyright 2018 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-ethereum library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 // +build !noclient,!noprotocol 18 19 package client 20 21 import ( 22 "context" 23 "errors" 24 "fmt" 25 "sync" 26 "time" 27 28 "github.com/ShyftNetwork/go-empyrean/common/hexutil" 29 "github.com/ShyftNetwork/go-empyrean/p2p" 30 "github.com/ShyftNetwork/go-empyrean/p2p/enode" 31 "github.com/ShyftNetwork/go-empyrean/p2p/protocols" 32 "github.com/ShyftNetwork/go-empyrean/rlp" 33 "github.com/ShyftNetwork/go-empyrean/rpc" 34 "github.com/ShyftNetwork/go-empyrean/swarm/log" 35 "github.com/ShyftNetwork/go-empyrean/swarm/pss" 36 ) 37 38 const ( 39 handshakeRetryTimeout = 1000 40 handshakeRetryCount = 3 41 ) 42 43 // The pss client provides devp2p emulation over pss RPC API, 44 // giving access to pss methods from a different process 45 type Client struct { 46 BaseAddrHex string 47 48 // peers 49 peerPool map[pss.Topic]map[string]*pssRPCRW 50 protos map[pss.Topic]*p2p.Protocol 51 52 // rpc connections 53 rpc *rpc.Client 54 subs []*rpc.ClientSubscription 55 56 // channels 57 topicsC chan []byte 58 quitC chan struct{} 59 60 poolMu sync.Mutex 61 } 62 63 // implements p2p.MsgReadWriter 64 type pssRPCRW struct { 65 *Client 66 topic string 67 msgC chan []byte 68 addr pss.PssAddress 69 pubKeyId string 70 lastSeen time.Time 71 closed bool 72 } 73 74 func (c *Client) newpssRPCRW(pubkeyid string, addr pss.PssAddress, topicobj pss.Topic) (*pssRPCRW, error) { 75 topic := topicobj.String() 76 err := c.rpc.Call(nil, "pss_setPeerPublicKey", pubkeyid, topic, hexutil.Encode(addr[:])) 77 if err != nil { 78 return nil, fmt.Errorf("setpeer %s %s: %v", topic, pubkeyid, err) 79 } 80 return &pssRPCRW{ 81 Client: c, 82 topic: topic, 83 msgC: make(chan []byte), 84 addr: addr, 85 pubKeyId: pubkeyid, 86 }, nil 87 } 88 89 func (rw *pssRPCRW) ReadMsg() (p2p.Msg, error) { 90 msg := <-rw.msgC 91 log.Trace("pssrpcrw read", "msg", msg) 92 pmsg, err := pss.ToP2pMsg(msg) 93 if err != nil { 94 return p2p.Msg{}, err 95 } 96 97 return pmsg, nil 98 } 99 100 // If only one message slot left 101 // then new is requested through handshake 102 // if buffer is empty, handshake request blocks until return 103 // after which pointer is changed to first new key in buffer 104 // will fail if: 105 // - any api calls fail 106 // - handshake retries are exhausted without reply, 107 // - send fails 108 func (rw *pssRPCRW) WriteMsg(msg p2p.Msg) error { 109 log.Trace("got writemsg pssclient", "msg", msg) 110 if rw.closed { 111 return fmt.Errorf("connection closed") 112 } 113 rlpdata := make([]byte, msg.Size) 114 msg.Payload.Read(rlpdata) 115 pmsg, err := rlp.EncodeToBytes(pss.ProtocolMsg{ 116 Code: msg.Code, 117 Size: msg.Size, 118 Payload: rlpdata, 119 }) 120 if err != nil { 121 return err 122 } 123 124 // Get the keys we have 125 var symkeyids []string 126 err = rw.Client.rpc.Call(&symkeyids, "pss_getHandshakeKeys", rw.pubKeyId, rw.topic, false, true) 127 if err != nil { 128 return err 129 } 130 131 // Check the capacity of the first key 132 var symkeycap uint16 133 if len(symkeyids) > 0 { 134 err = rw.Client.rpc.Call(&symkeycap, "pss_getHandshakeKeyCapacity", symkeyids[0]) 135 if err != nil { 136 return err 137 } 138 } 139 140 err = rw.Client.rpc.Call(nil, "pss_sendSym", symkeyids[0], rw.topic, hexutil.Encode(pmsg)) 141 if err != nil { 142 return err 143 } 144 145 // If this is the last message it is valid for, initiate new handshake 146 if symkeycap == 1 { 147 var retries int 148 var sync bool 149 // if it's the only remaining key, make sure we don't continue until we have new ones for further writes 150 if len(symkeyids) == 1 { 151 sync = true 152 } 153 // initiate handshake 154 _, err := rw.handshake(retries, sync, false) 155 if err != nil { 156 log.Warn("failing", "err", err) 157 return err 158 } 159 } 160 return nil 161 } 162 163 // retry and synchronicity wrapper for handshake api call 164 // returns first new symkeyid upon successful execution 165 func (rw *pssRPCRW) handshake(retries int, sync bool, flush bool) (string, error) { 166 167 var symkeyids []string 168 var i int 169 // request new keys 170 // if the key buffer was depleted, make this as a blocking call and try several times before giving up 171 for i = 0; i < 1+retries; i++ { 172 log.Debug("handshake attempt pssrpcrw", "pubkeyid", rw.pubKeyId, "topic", rw.topic, "sync", sync) 173 err := rw.Client.rpc.Call(&symkeyids, "pss_handshake", rw.pubKeyId, rw.topic, sync, flush) 174 if err == nil { 175 var keyid string 176 if sync { 177 keyid = symkeyids[0] 178 } 179 return keyid, nil 180 } 181 if i-1+retries > 1 { 182 time.Sleep(time.Millisecond * handshakeRetryTimeout) 183 } 184 } 185 186 return "", fmt.Errorf("handshake failed after %d attempts", i) 187 } 188 189 // Custom constructor 190 // 191 // Provides direct access to the rpc object 192 func NewClient(rpcurl string) (*Client, error) { 193 rpcclient, err := rpc.Dial(rpcurl) 194 if err != nil { 195 return nil, err 196 } 197 198 client, err := NewClientWithRPC(rpcclient) 199 if err != nil { 200 return nil, err 201 } 202 return client, nil 203 } 204 205 // Main constructor 206 // 207 // The 'rpcclient' parameter allows passing a in-memory rpc client to act as the remote websocket RPC. 208 func NewClientWithRPC(rpcclient *rpc.Client) (*Client, error) { 209 client := newClient() 210 client.rpc = rpcclient 211 err := client.rpc.Call(&client.BaseAddrHex, "pss_baseAddr") 212 if err != nil { 213 return nil, fmt.Errorf("cannot get pss node baseaddress: %v", err) 214 } 215 return client, nil 216 } 217 218 func newClient() (client *Client) { 219 client = &Client{ 220 quitC: make(chan struct{}), 221 peerPool: make(map[pss.Topic]map[string]*pssRPCRW), 222 protos: make(map[pss.Topic]*p2p.Protocol), 223 } 224 return 225 } 226 227 // Mounts a new devp2p protcool on the pss connection 228 // 229 // the protocol is aliased as a "pss topic" 230 // uses normal devp2p send and incoming message handler routines from the p2p/protocols package 231 // 232 // when an incoming message is received from a peer that is not yet known to the client, 233 // this peer object is instantiated, and the protocol is run on it. 234 func (c *Client) RunProtocol(ctx context.Context, proto *p2p.Protocol) error { 235 topicobj := pss.BytesToTopic([]byte(fmt.Sprintf("%s:%d", proto.Name, proto.Version))) 236 topichex := topicobj.String() 237 msgC := make(chan pss.APIMsg) 238 c.peerPool[topicobj] = make(map[string]*pssRPCRW) 239 sub, err := c.rpc.Subscribe(ctx, "pss", msgC, "receive", topichex, false, false) 240 if err != nil { 241 return fmt.Errorf("pss event subscription failed: %v", err) 242 } 243 c.subs = append(c.subs, sub) 244 err = c.rpc.Call(nil, "pss_addHandshake", topichex) 245 if err != nil { 246 return fmt.Errorf("pss handshake activation failed: %v", err) 247 } 248 249 // dispatch incoming messages 250 go func() { 251 for { 252 select { 253 case msg := <-msgC: 254 // we only allow sym msgs here 255 if msg.Asymmetric { 256 continue 257 } 258 // we get passed the symkeyid 259 // need the symkey itself to resolve to peer's pubkey 260 var pubkeyid string 261 err = c.rpc.Call(&pubkeyid, "pss_getHandshakePublicKey", msg.Key) 262 if err != nil || pubkeyid == "" { 263 log.Trace("proto err or no pubkey", "err", err, "symkeyid", msg.Key) 264 continue 265 } 266 // if we don't have the peer on this protocol already, create it 267 // this is more or less the same as AddPssPeer, less the handshake initiation 268 if c.peerPool[topicobj][pubkeyid] == nil { 269 var addrhex string 270 err := c.rpc.Call(&addrhex, "pss_getAddress", topichex, false, msg.Key) 271 if err != nil { 272 log.Trace(err.Error()) 273 continue 274 } 275 addrbytes, err := hexutil.Decode(addrhex) 276 if err != nil { 277 log.Trace(err.Error()) 278 break 279 } 280 addr := pss.PssAddress(addrbytes) 281 rw, err := c.newpssRPCRW(pubkeyid, addr, topicobj) 282 if err != nil { 283 break 284 } 285 c.peerPool[topicobj][pubkeyid] = rw 286 p := p2p.NewPeer(enode.ID{}, fmt.Sprintf("%v", addr), []p2p.Cap{}) 287 go proto.Run(p, c.peerPool[topicobj][pubkeyid]) 288 } 289 go func() { 290 c.peerPool[topicobj][pubkeyid].msgC <- msg.Msg 291 }() 292 case <-c.quitC: 293 return 294 } 295 } 296 }() 297 298 c.protos[topicobj] = proto 299 return nil 300 } 301 302 // Always call this to ensure that we exit cleanly 303 func (c *Client) Close() error { 304 for _, s := range c.subs { 305 s.Unsubscribe() 306 } 307 return nil 308 } 309 310 // Add a pss peer (public key) and run the protocol on it 311 // 312 // client.RunProtocol with matching topic must have been 313 // run prior to adding the peer, or this method will 314 // return an error. 315 // 316 // The key must exist in the key store of the pss node 317 // before the peer is added. The method will return an error 318 // if it is not. 319 func (c *Client) AddPssPeer(pubkeyid string, addr []byte, spec *protocols.Spec) error { 320 topic := pss.ProtocolTopic(spec) 321 if c.peerPool[topic] == nil { 322 return errors.New("addpeer on unset topic") 323 } 324 if c.peerPool[topic][pubkeyid] == nil { 325 rw, err := c.newpssRPCRW(pubkeyid, addr, topic) 326 if err != nil { 327 return err 328 } 329 _, err = rw.handshake(handshakeRetryCount, true, true) 330 if err != nil { 331 return err 332 } 333 c.poolMu.Lock() 334 c.peerPool[topic][pubkeyid] = rw 335 c.poolMu.Unlock() 336 p := p2p.NewPeer(enode.ID{}, fmt.Sprintf("%v", addr), []p2p.Cap{}) 337 go c.protos[topic].Run(p, c.peerPool[topic][pubkeyid]) 338 } 339 return nil 340 } 341 342 // Remove a pss peer 343 // 344 // TODO: underlying cleanup 345 func (c *Client) RemovePssPeer(pubkeyid string, spec *protocols.Spec) { 346 log.Debug("closing pss client peer", "pubkey", pubkeyid, "protoname", spec.Name, "protoversion", spec.Version) 347 c.poolMu.Lock() 348 defer c.poolMu.Unlock() 349 topic := pss.ProtocolTopic(spec) 350 c.peerPool[topic][pubkeyid].closed = true 351 delete(c.peerPool[topic], pubkeyid) 352 }