2021-04-28 20:10:44 +00:00
|
|
|
package lightpush
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"encoding/hex"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
2021-11-25 13:46:04 +00:00
|
|
|
"math"
|
2021-04-28 20:10:44 +00:00
|
|
|
|
|
|
|
"github.com/libp2p/go-libp2p-core/host"
|
|
|
|
"github.com/libp2p/go-libp2p-core/network"
|
|
|
|
libp2pProtocol "github.com/libp2p/go-libp2p-core/protocol"
|
|
|
|
"github.com/libp2p/go-msgio/protoio"
|
2021-10-30 23:19:03 +00:00
|
|
|
"github.com/status-im/go-waku/waku/v2/metrics"
|
2021-04-28 20:10:44 +00:00
|
|
|
"github.com/status-im/go-waku/waku/v2/protocol"
|
|
|
|
"github.com/status-im/go-waku/waku/v2/protocol/pb"
|
2021-06-16 10:14:22 +00:00
|
|
|
"github.com/status-im/go-waku/waku/v2/protocol/relay"
|
2022-01-18 18:17:06 +00:00
|
|
|
"go.uber.org/zap"
|
2021-04-28 20:10:44 +00:00
|
|
|
)
|
|
|
|
|
2021-09-30 15:59:51 +00:00
|
|
|
const LightPushID_v20beta1 = libp2pProtocol.ID("/vac/waku/lightpush/2.0.0-beta1")
|
2021-04-28 20:10:44 +00:00
|
|
|
|
|
|
|
var (
|
|
|
|
ErrNoPeersAvailable = errors.New("no suitable remote peers")
|
|
|
|
ErrInvalidId = errors.New("invalid request id")
|
|
|
|
)
|
|
|
|
|
|
|
|
type WakuLightPush struct {
|
|
|
|
h host.Host
|
|
|
|
relay *relay.WakuRelay
|
|
|
|
ctx context.Context
|
2021-12-06 08:43:00 +00:00
|
|
|
|
2022-01-18 18:17:06 +00:00
|
|
|
log *zap.SugaredLogger
|
|
|
|
|
2021-12-06 08:43:00 +00:00
|
|
|
started bool
|
2021-04-28 20:10:44 +00:00
|
|
|
}
|
|
|
|
|
2022-05-04 21:08:24 +00:00
|
|
|
// NewWakuRelay returns a new instance of Waku Lightpush struct
|
2022-01-18 18:17:06 +00:00
|
|
|
func NewWakuLightPush(ctx context.Context, h host.Host, relay *relay.WakuRelay, log *zap.SugaredLogger) *WakuLightPush {
|
2021-04-28 20:10:44 +00:00
|
|
|
wakuLP := new(WakuLightPush)
|
|
|
|
wakuLP.relay = relay
|
|
|
|
wakuLP.ctx = ctx
|
|
|
|
wakuLP.h = h
|
2022-01-18 18:17:06 +00:00
|
|
|
wakuLP.log = log.Named("lightpush")
|
2021-04-28 20:23:03 +00:00
|
|
|
|
2021-11-01 12:38:03 +00:00
|
|
|
return wakuLP
|
|
|
|
}
|
|
|
|
|
2022-05-04 21:08:24 +00:00
|
|
|
// Start inits the lighpush protocol
|
2021-11-01 12:38:03 +00:00
|
|
|
func (wakuLP *WakuLightPush) Start() error {
|
2021-11-07 12:08:29 +00:00
|
|
|
if wakuLP.IsClientOnly() {
|
|
|
|
return errors.New("relay is required, without it, it is only a client and cannot be started")
|
2021-10-28 12:41:17 +00:00
|
|
|
}
|
2021-04-28 20:23:03 +00:00
|
|
|
|
2021-11-01 12:38:03 +00:00
|
|
|
wakuLP.h.SetStreamHandlerMatch(LightPushID_v20beta1, protocol.PrefixTextMatch(string(LightPushID_v20beta1)), wakuLP.onRequest)
|
2022-01-18 18:17:06 +00:00
|
|
|
wakuLP.log.Info("Light Push protocol started")
|
2021-12-06 08:43:00 +00:00
|
|
|
wakuLP.started = true
|
2021-11-01 12:38:03 +00:00
|
|
|
|
|
|
|
return nil
|
2021-04-28 20:10:44 +00:00
|
|
|
}
|
|
|
|
|
2022-05-04 21:08:24 +00:00
|
|
|
// IsClientOnly determines if this node supports relaying messages for other lightpush clients
|
2021-11-07 12:08:29 +00:00
|
|
|
func (wakuLp *WakuLightPush) IsClientOnly() bool {
|
|
|
|
return wakuLp.relay == nil
|
|
|
|
}
|
|
|
|
|
2021-04-28 20:10:44 +00:00
|
|
|
func (wakuLP *WakuLightPush) onRequest(s network.Stream) {
|
|
|
|
defer s.Close()
|
|
|
|
|
|
|
|
requestPushRPC := &pb.PushRPC{}
|
|
|
|
|
|
|
|
writer := protoio.NewDelimitedWriter(s)
|
2021-11-25 14:18:33 +00:00
|
|
|
reader := protoio.NewDelimitedReader(s, math.MaxInt32)
|
2021-04-28 20:10:44 +00:00
|
|
|
|
|
|
|
err := reader.ReadMsg(requestPushRPC)
|
|
|
|
if err != nil {
|
2022-01-18 18:17:06 +00:00
|
|
|
wakuLP.log.Error("error reading request", err)
|
2021-10-30 23:19:03 +00:00
|
|
|
metrics.RecordLightpushError(wakuLP.ctx, "decodeRpcFailure")
|
2021-04-28 20:10:44 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-01-18 18:17:06 +00:00
|
|
|
wakuLP.log.Info(fmt.Sprintf("%s: lightpush message received from %s", s.Conn().LocalPeer(), s.Conn().RemotePeer()))
|
2021-04-28 20:10:44 +00:00
|
|
|
|
|
|
|
if requestPushRPC.Query != nil {
|
2022-01-18 18:17:06 +00:00
|
|
|
wakuLP.log.Info("lightpush push request")
|
2021-04-28 20:10:44 +00:00
|
|
|
response := new(pb.PushResponse)
|
2021-11-07 12:08:29 +00:00
|
|
|
if !wakuLP.IsClientOnly() {
|
2021-11-19 16:19:48 +00:00
|
|
|
pubSubTopic := requestPushRPC.Query.PubsubTopic
|
2021-11-07 12:08:29 +00:00
|
|
|
message := requestPushRPC.Query.Message
|
|
|
|
|
2021-11-01 12:38:03 +00:00
|
|
|
// TODO: Assumes success, should probably be extended to check for network, peers, etc
|
|
|
|
// It might make sense to use WithReadiness option here?
|
|
|
|
|
2021-11-20 00:03:05 +00:00
|
|
|
_, err := wakuLP.relay.PublishToTopic(wakuLP.ctx, message, pubSubTopic)
|
2021-04-28 20:10:44 +00:00
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
response.IsSuccess = false
|
|
|
|
response.Info = "Could not publish message"
|
|
|
|
} else {
|
|
|
|
response.IsSuccess = true
|
2021-06-28 14:14:28 +00:00
|
|
|
response.Info = "Totally" // TODO: ask about this
|
2021-04-28 20:10:44 +00:00
|
|
|
}
|
|
|
|
} else {
|
2022-01-18 18:17:06 +00:00
|
|
|
wakuLP.log.Debug("no relay protocol present, unsuccessful push")
|
2021-04-28 20:10:44 +00:00
|
|
|
response.IsSuccess = false
|
|
|
|
response.Info = "No relay protocol"
|
|
|
|
}
|
|
|
|
|
|
|
|
responsePushRPC := &pb.PushRPC{}
|
|
|
|
responsePushRPC.RequestId = requestPushRPC.RequestId
|
|
|
|
responsePushRPC.Response = response
|
|
|
|
|
|
|
|
err = writer.WriteMsg(responsePushRPC)
|
|
|
|
if err != nil {
|
2022-01-18 18:17:06 +00:00
|
|
|
wakuLP.log.Error("error writing response", err)
|
2021-07-29 12:40:54 +00:00
|
|
|
_ = s.Reset()
|
2021-04-28 20:10:44 +00:00
|
|
|
} else {
|
2022-01-18 18:17:06 +00:00
|
|
|
wakuLP.log.Info(fmt.Sprintf("%s: response sent to %s", s.Conn().LocalPeer().String(), s.Conn().RemotePeer().String()))
|
2021-04-28 20:10:44 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if requestPushRPC.Response != nil {
|
|
|
|
if requestPushRPC.Response.IsSuccess {
|
2022-01-18 18:17:06 +00:00
|
|
|
wakuLP.log.Info("lightpush message success")
|
2021-04-28 20:10:44 +00:00
|
|
|
} else {
|
2022-01-18 18:17:06 +00:00
|
|
|
wakuLP.log.Info(fmt.Sprintf("lightpush message failure. info=%s", requestPushRPC.Response.Info))
|
2021-04-28 20:10:44 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-01 12:38:03 +00:00
|
|
|
func (wakuLP *WakuLightPush) request(ctx context.Context, req *pb.PushRequest, opts ...LightPushOption) (*pb.PushResponse, error) {
|
2021-04-28 20:10:44 +00:00
|
|
|
params := new(LightPushParameters)
|
2021-11-09 23:34:04 +00:00
|
|
|
params.host = wakuLP.h
|
2022-01-18 18:17:06 +00:00
|
|
|
params.log = wakuLP.log
|
2021-04-28 20:10:44 +00:00
|
|
|
|
2021-11-07 12:08:29 +00:00
|
|
|
optList := DefaultOptions(wakuLP.h)
|
2021-04-28 20:10:44 +00:00
|
|
|
optList = append(optList, opts...)
|
|
|
|
for _, opt := range optList {
|
|
|
|
opt(params)
|
|
|
|
}
|
|
|
|
|
|
|
|
if params.selectedPeer == "" {
|
2021-10-30 23:19:03 +00:00
|
|
|
metrics.RecordLightpushError(wakuLP.ctx, "dialError")
|
2021-04-28 20:10:44 +00:00
|
|
|
return nil, ErrNoPeersAvailable
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(params.requestId) == 0 {
|
|
|
|
return nil, ErrInvalidId
|
|
|
|
}
|
|
|
|
|
2022-03-03 16:04:03 +00:00
|
|
|
// We connect first so dns4 addresses are resolved (NewStream does not do it)
|
|
|
|
err := wakuLP.h.Connect(ctx, wakuLP.h.Peerstore().PeerInfo(params.selectedPeer))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2021-09-30 15:59:51 +00:00
|
|
|
connOpt, err := wakuLP.h.NewStream(ctx, params.selectedPeer, LightPushID_v20beta1)
|
2021-04-28 20:10:44 +00:00
|
|
|
if err != nil {
|
2022-01-18 18:17:06 +00:00
|
|
|
wakuLP.log.Info("failed to connect to remote peer", err)
|
2021-10-30 23:19:03 +00:00
|
|
|
metrics.RecordLightpushError(wakuLP.ctx, "dialError")
|
2021-04-28 20:10:44 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
defer connOpt.Close()
|
2021-08-13 11:56:09 +00:00
|
|
|
defer func() {
|
|
|
|
err := connOpt.Reset()
|
|
|
|
if err != nil {
|
2021-10-30 23:19:03 +00:00
|
|
|
metrics.RecordLightpushError(wakuLP.ctx, "dialError")
|
2022-01-18 18:17:06 +00:00
|
|
|
wakuLP.log.Error("failed to reset connection", err)
|
2021-08-13 11:56:09 +00:00
|
|
|
}
|
|
|
|
}()
|
2021-04-28 20:10:44 +00:00
|
|
|
|
|
|
|
pushRequestRPC := &pb.PushRPC{RequestId: hex.EncodeToString(params.requestId), Query: req}
|
|
|
|
|
|
|
|
writer := protoio.NewDelimitedWriter(connOpt)
|
2021-11-25 14:18:33 +00:00
|
|
|
reader := protoio.NewDelimitedReader(connOpt, math.MaxInt32)
|
2021-04-28 20:10:44 +00:00
|
|
|
|
|
|
|
err = writer.WriteMsg(pushRequestRPC)
|
|
|
|
if err != nil {
|
2022-01-18 18:17:06 +00:00
|
|
|
wakuLP.log.Error("could not write request", err)
|
2021-04-28 20:10:44 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
pushResponseRPC := &pb.PushRPC{}
|
|
|
|
err = reader.ReadMsg(pushResponseRPC)
|
|
|
|
if err != nil {
|
2022-01-18 18:17:06 +00:00
|
|
|
wakuLP.log.Error("could not read response", err)
|
2021-10-30 23:19:03 +00:00
|
|
|
metrics.RecordLightpushError(wakuLP.ctx, "decodeRPCFailure")
|
2021-04-28 20:10:44 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return pushResponseRPC.Response, nil
|
|
|
|
}
|
2021-10-11 22:45:54 +00:00
|
|
|
|
2022-05-04 21:08:24 +00:00
|
|
|
// IsStarted returns if the lightpush protocol has been mounted or not
|
2021-12-06 08:43:00 +00:00
|
|
|
func (wakuLP *WakuLightPush) IsStarted() bool {
|
|
|
|
return wakuLP.started
|
|
|
|
}
|
|
|
|
|
2022-05-04 21:08:24 +00:00
|
|
|
// Stop unmounts the lightpush protocol
|
2021-11-05 20:09:48 +00:00
|
|
|
func (wakuLP *WakuLightPush) Stop() {
|
|
|
|
wakuLP.h.RemoveStreamHandler(LightPushID_v20beta1)
|
2021-12-06 08:43:00 +00:00
|
|
|
wakuLP.started = false
|
2021-10-11 22:45:54 +00:00
|
|
|
}
|
2021-11-01 12:38:03 +00:00
|
|
|
|
2022-05-04 21:08:24 +00:00
|
|
|
// PublishToTopic is used to broadcast a WakuMessage to a pubsub topic via lightpush protocol
|
2021-11-20 00:03:05 +00:00
|
|
|
func (wakuLP *WakuLightPush) PublishToTopic(ctx context.Context, message *pb.WakuMessage, topic string, opts ...LightPushOption) ([]byte, error) {
|
2021-11-01 12:38:03 +00:00
|
|
|
if message == nil {
|
|
|
|
return nil, errors.New("message can't be null")
|
|
|
|
}
|
|
|
|
|
|
|
|
req := new(pb.PushRequest)
|
|
|
|
req.Message = message
|
2021-11-19 20:01:52 +00:00
|
|
|
req.PubsubTopic = topic
|
2021-11-01 12:38:03 +00:00
|
|
|
|
2021-11-05 20:09:48 +00:00
|
|
|
response, err := wakuLP.request(ctx, req, opts...)
|
2021-11-01 12:38:03 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if response.IsSuccess {
|
|
|
|
hash, _ := message.Hash()
|
|
|
|
return hash, nil
|
|
|
|
} else {
|
|
|
|
return nil, errors.New(response.Info)
|
|
|
|
}
|
|
|
|
}
|
2021-11-19 20:01:52 +00:00
|
|
|
|
2022-05-04 21:08:24 +00:00
|
|
|
// Publish is used to broadcast a WakuMessage to the default waku pubsub topic via lightpush protocol
|
2021-11-19 20:01:52 +00:00
|
|
|
func (wakuLP *WakuLightPush) Publish(ctx context.Context, message *pb.WakuMessage, opts ...LightPushOption) ([]byte, error) {
|
2021-11-20 00:03:05 +00:00
|
|
|
return wakuLP.PublishToTopic(ctx, message, relay.DefaultWakuTopic, opts...)
|
2021-11-19 20:01:52 +00:00
|
|
|
}
|