2021-04-28 20:10:44 +00:00
|
|
|
package lightpush
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"encoding/hex"
|
|
|
|
"errors"
|
2023-10-24 16:26:02 +00:00
|
|
|
"fmt"
|
2021-11-25 13:46:04 +00:00
|
|
|
"math"
|
2021-04-28 20:10:44 +00:00
|
|
|
|
2022-10-19 19:39:32 +00:00
|
|
|
"github.com/libp2p/go-libp2p/core/host"
|
|
|
|
"github.com/libp2p/go-libp2p/core/network"
|
|
|
|
libp2pProtocol "github.com/libp2p/go-libp2p/core/protocol"
|
2023-02-06 22:16:20 +00:00
|
|
|
"github.com/libp2p/go-msgio/pbio"
|
2023-08-16 01:40:00 +00:00
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
2022-11-09 19:53:01 +00:00
|
|
|
"github.com/waku-org/go-waku/logging"
|
2023-08-10 12:58:22 +00:00
|
|
|
"github.com/waku-org/go-waku/waku/v2/peermanager"
|
2022-11-09 19:53:01 +00:00
|
|
|
"github.com/waku-org/go-waku/waku/v2/protocol"
|
2023-02-06 22:16:20 +00:00
|
|
|
"github.com/waku-org/go-waku/waku/v2/protocol/lightpush/pb"
|
|
|
|
wpb "github.com/waku-org/go-waku/waku/v2/protocol/pb"
|
2022-11-09 19:53:01 +00:00
|
|
|
"github.com/waku-org/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
|
|
|
)
|
|
|
|
|
2023-07-19 16:25:35 +00:00
|
|
|
// LightPushID_v20beta1 is the current Waku LightPush protocol identifier
|
2021-09-30 15:59:51 +00:00
|
|
|
const LightPushID_v20beta1 = libp2pProtocol.ID("/vac/waku/lightpush/2.0.0-beta1")
|
2023-11-07 17:13:19 +00:00
|
|
|
const LightPushENRField = uint8(1 << 3)
|
2021-04-28 20:10:44 +00:00
|
|
|
|
|
|
|
var (
|
|
|
|
ErrNoPeersAvailable = errors.New("no suitable remote peers")
|
2023-09-11 14:24:05 +00:00
|
|
|
ErrInvalidID = errors.New("invalid request id")
|
2021-04-28 20:10:44 +00:00
|
|
|
)
|
|
|
|
|
2023-07-19 16:25:35 +00:00
|
|
|
// WakuLightPush is the implementation of the Waku LightPush protocol
|
2021-04-28 20:10:44 +00:00
|
|
|
type WakuLightPush struct {
|
2023-08-16 01:40:00 +00:00
|
|
|
h host.Host
|
|
|
|
relay *relay.WakuRelay
|
|
|
|
cancel context.CancelFunc
|
|
|
|
pm *peermanager.PeerManager
|
|
|
|
metrics Metrics
|
2021-12-06 08:43:00 +00:00
|
|
|
|
2022-05-30 15:55:30 +00:00
|
|
|
log *zap.Logger
|
2021-04-28 20:10:44 +00:00
|
|
|
}
|
|
|
|
|
2023-08-03 16:21:15 +00:00
|
|
|
// NewWakuLightPush returns a new instance of Waku Lightpush struct
|
2023-08-10 12:58:22 +00:00
|
|
|
// Takes an optional peermanager if WakuLightPush is being created along with WakuNode.
|
|
|
|
// If using libp2p host, then pass peermanager as nil
|
2023-08-16 01:40:00 +00:00
|
|
|
func NewWakuLightPush(relay *relay.WakuRelay, pm *peermanager.PeerManager, reg prometheus.Registerer, log *zap.Logger) *WakuLightPush {
|
2021-04-28 20:10:44 +00:00
|
|
|
wakuLP := new(WakuLightPush)
|
|
|
|
wakuLP.relay = relay
|
2022-01-18 18:17:06 +00:00
|
|
|
wakuLP.log = log.Named("lightpush")
|
2023-08-10 12:58:22 +00:00
|
|
|
wakuLP.pm = pm
|
2023-08-16 01:40:00 +00:00
|
|
|
wakuLP.metrics = newMetrics(reg)
|
2023-11-07 17:13:19 +00:00
|
|
|
|
2021-11-01 12:38:03 +00:00
|
|
|
return wakuLP
|
|
|
|
}
|
|
|
|
|
2023-04-17 00:04:12 +00:00
|
|
|
// Sets the host to be able to mount or consume a protocol
|
|
|
|
func (wakuLP *WakuLightPush) SetHost(h host.Host) {
|
|
|
|
wakuLP.h = h
|
|
|
|
}
|
|
|
|
|
2022-05-04 21:08:24 +00:00
|
|
|
// Start inits the lighpush protocol
|
2023-01-06 22:37:57 +00:00
|
|
|
func (wakuLP *WakuLightPush) Start(ctx context.Context) error {
|
2022-10-20 13:42:01 +00:00
|
|
|
if wakuLP.relayIsNotAvailable() {
|
2021-11-07 12:08:29 +00:00
|
|
|
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
|
|
|
|
2023-01-06 22:37:57 +00:00
|
|
|
ctx, cancel := context.WithCancel(ctx)
|
|
|
|
|
|
|
|
wakuLP.cancel = cancel
|
|
|
|
wakuLP.h.SetStreamHandlerMatch(LightPushID_v20beta1, protocol.PrefixTextMatch(string(LightPushID_v20beta1)), wakuLP.onRequest(ctx))
|
2022-01-18 18:17:06 +00:00
|
|
|
wakuLP.log.Info("Light Push protocol started")
|
2021-11-01 12:38:03 +00:00
|
|
|
|
2023-11-07 17:13:19 +00:00
|
|
|
if wakuLP.pm != nil {
|
|
|
|
wakuLP.pm.RegisterWakuProtocol(LightPushID_v20beta1, LightPushENRField)
|
|
|
|
}
|
2021-11-01 12:38:03 +00:00
|
|
|
return nil
|
2021-04-28 20:10:44 +00:00
|
|
|
}
|
|
|
|
|
2022-10-20 13:42:01 +00:00
|
|
|
// relayIsNotAvailable determines if this node supports relaying messages for other lightpush clients
|
2023-09-11 14:24:05 +00:00
|
|
|
func (wakuLP *WakuLightPush) relayIsNotAvailable() bool {
|
|
|
|
return wakuLP.relay == nil
|
2021-11-07 12:08:29 +00:00
|
|
|
}
|
|
|
|
|
2023-10-22 01:34:52 +00:00
|
|
|
func (wakuLP *WakuLightPush) onRequest(ctx context.Context) func(network.Stream) {
|
|
|
|
return func(stream network.Stream) {
|
|
|
|
logger := wakuLP.log.With(logging.HostID("peer", stream.Conn().RemotePeer()))
|
2023-01-06 22:37:57 +00:00
|
|
|
requestPushRPC := &pb.PushRPC{}
|
2021-04-28 20:10:44 +00:00
|
|
|
|
2023-10-22 01:34:52 +00:00
|
|
|
reader := pbio.NewDelimitedReader(stream, math.MaxInt32)
|
2021-04-28 20:10:44 +00:00
|
|
|
|
2023-01-06 22:37:57 +00:00
|
|
|
err := reader.ReadMsg(requestPushRPC)
|
|
|
|
if err != nil {
|
|
|
|
logger.Error("reading request", zap.Error(err))
|
2023-08-16 01:40:00 +00:00
|
|
|
wakuLP.metrics.RecordError(decodeRPCFailure)
|
2023-10-22 01:34:52 +00:00
|
|
|
if err := stream.Reset(); err != nil {
|
|
|
|
wakuLP.log.Error("resetting connection", zap.Error(err))
|
|
|
|
}
|
2023-01-06 22:37:57 +00:00
|
|
|
return
|
|
|
|
}
|
2021-04-28 20:10:44 +00:00
|
|
|
|
2023-10-24 16:26:02 +00:00
|
|
|
responsePushRPC := &pb.PushRPC{
|
|
|
|
RequestId: requestPushRPC.RequestId,
|
|
|
|
Response: &pb.PushResponse{},
|
|
|
|
}
|
2023-10-22 01:34:52 +00:00
|
|
|
|
2023-10-24 16:26:02 +00:00
|
|
|
if err := requestPushRPC.ValidateRequest(); err != nil {
|
|
|
|
responsePushRPC.Response.Info = err.Error()
|
|
|
|
wakuLP.metrics.RecordError(requestBodyFailure)
|
|
|
|
wakuLP.reply(stream, responsePushRPC, logger)
|
|
|
|
return
|
|
|
|
}
|
2023-10-22 01:34:52 +00:00
|
|
|
|
2023-10-24 16:26:02 +00:00
|
|
|
logger = logger.With(zap.String("requestID", requestPushRPC.RequestId))
|
2023-01-08 18:33:30 +00:00
|
|
|
|
2023-10-24 16:26:02 +00:00
|
|
|
logger.Info("push request")
|
2023-01-08 18:33:30 +00:00
|
|
|
|
2023-10-24 16:26:02 +00:00
|
|
|
pubSubTopic := requestPushRPC.Query.PubsubTopic
|
|
|
|
message := requestPushRPC.Query.Message
|
2023-04-19 16:09:03 +00:00
|
|
|
|
2023-10-24 16:26:02 +00:00
|
|
|
wakuLP.metrics.RecordMessage()
|
2023-01-08 18:33:30 +00:00
|
|
|
|
2023-10-24 16:26:02 +00:00
|
|
|
// TODO: Assumes success, should probably be extended to check for network, peers, etc
|
|
|
|
// It might make sense to use WithReadiness option here?
|
2023-01-08 18:33:30 +00:00
|
|
|
|
2023-10-30 16:30:25 +00:00
|
|
|
_, err = wakuLP.relay.Publish(ctx, message, relay.WithPubSubTopic(pubSubTopic))
|
2023-10-24 16:26:02 +00:00
|
|
|
if err != nil {
|
|
|
|
logger.Error("publishing message", zap.Error(err))
|
|
|
|
wakuLP.metrics.RecordError(messagePushFailure)
|
|
|
|
responsePushRPC.Response.Info = fmt.Sprintf("Could not publish message: %s", err.Error())
|
|
|
|
return
|
|
|
|
} else {
|
|
|
|
responsePushRPC.Response.IsSuccess = true
|
|
|
|
responsePushRPC.Response.Info = "OK"
|
|
|
|
}
|
2021-04-28 20:10:44 +00:00
|
|
|
|
2023-10-24 16:26:02 +00:00
|
|
|
wakuLP.reply(stream, responsePushRPC, logger)
|
2021-04-28 20:10:44 +00:00
|
|
|
|
2023-10-24 16:26:02 +00:00
|
|
|
logger.Info("response sent")
|
2023-10-22 01:34:52 +00:00
|
|
|
|
2023-10-24 16:26:02 +00:00
|
|
|
stream.Close()
|
|
|
|
|
|
|
|
if responsePushRPC.Response.IsSuccess {
|
|
|
|
logger.Info("request success")
|
2023-04-19 16:09:03 +00:00
|
|
|
} else {
|
2023-10-24 16:26:02 +00:00
|
|
|
logger.Info("request failure", zap.String("info", responsePushRPC.Response.Info))
|
2021-04-28 20:10:44 +00:00
|
|
|
}
|
2023-10-24 16:26:02 +00:00
|
|
|
}
|
|
|
|
}
|
2021-04-28 20:10:44 +00:00
|
|
|
|
2023-10-24 16:26:02 +00:00
|
|
|
func (wakuLP *WakuLightPush) reply(stream network.Stream, responsePushRPC *pb.PushRPC, logger *zap.Logger) {
|
|
|
|
writer := pbio.NewDelimitedWriter(stream)
|
|
|
|
err := writer.WriteMsg(responsePushRPC)
|
|
|
|
if err != nil {
|
|
|
|
wakuLP.metrics.RecordError(writeResponseFailure)
|
|
|
|
logger.Error("writing response", zap.Error(err))
|
|
|
|
if err := stream.Reset(); err != nil {
|
|
|
|
wakuLP.log.Error("resetting connection", zap.Error(err))
|
2021-04-28 20:10:44 +00:00
|
|
|
}
|
2023-10-24 16:26:02 +00:00
|
|
|
return
|
2021-04-28 20:10:44 +00:00
|
|
|
}
|
2023-10-24 16:26:02 +00:00
|
|
|
stream.Close()
|
2021-04-28 20:10:44 +00:00
|
|
|
}
|
|
|
|
|
2023-09-29 05:13:25 +00:00
|
|
|
// request sends a message via lightPush protocol to either a specified peer or peer that is selected.
|
|
|
|
func (wakuLP *WakuLightPush) request(ctx context.Context, req *pb.PushRequest, params *lightPushParameters) (*pb.PushResponse, error) {
|
|
|
|
if params == nil {
|
|
|
|
return nil, errors.New("lightpush params are mandatory")
|
2021-04-28 20:10:44 +00:00
|
|
|
}
|
|
|
|
|
2023-07-19 16:25:35 +00:00
|
|
|
if len(params.requestID) == 0 {
|
2023-09-11 14:24:05 +00:00
|
|
|
return nil, ErrInvalidID
|
2021-04-28 20:10:44 +00:00
|
|
|
}
|
|
|
|
|
2022-05-30 15:55:30 +00:00
|
|
|
logger := wakuLP.log.With(logging.HostID("peer", params.selectedPeer))
|
2022-03-03 16:04:03 +00:00
|
|
|
|
2023-10-20 21:15:51 +00:00
|
|
|
stream, err := wakuLP.h.NewStream(ctx, params.selectedPeer, LightPushID_v20beta1)
|
2021-04-28 20:10:44 +00:00
|
|
|
if err != nil {
|
2022-05-30 15:55:30 +00:00
|
|
|
logger.Error("creating stream to peer", zap.Error(err))
|
2023-08-16 01:40:00 +00:00
|
|
|
wakuLP.metrics.RecordError(dialFailure)
|
2021-04-28 20:10:44 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
2023-07-19 16:25:35 +00:00
|
|
|
pushRequestRPC := &pb.PushRPC{RequestId: hex.EncodeToString(params.requestID), Query: req}
|
2021-04-28 20:10:44 +00:00
|
|
|
|
2023-10-20 21:15:51 +00:00
|
|
|
writer := pbio.NewDelimitedWriter(stream)
|
|
|
|
reader := pbio.NewDelimitedReader(stream, math.MaxInt32)
|
2021-04-28 20:10:44 +00:00
|
|
|
|
|
|
|
err = writer.WriteMsg(pushRequestRPC)
|
|
|
|
if err != nil {
|
2023-08-16 01:40:00 +00:00
|
|
|
wakuLP.metrics.RecordError(writeRequestFailure)
|
2022-05-30 15:55:30 +00:00
|
|
|
logger.Error("writing request", zap.Error(err))
|
2023-10-22 01:34:52 +00:00
|
|
|
if err := stream.Reset(); err != nil {
|
|
|
|
wakuLP.log.Error("resetting connection", zap.Error(err))
|
|
|
|
}
|
2021-04-28 20:10:44 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
pushResponseRPC := &pb.PushRPC{}
|
|
|
|
err = reader.ReadMsg(pushResponseRPC)
|
|
|
|
if err != nil {
|
2022-05-30 15:55:30 +00:00
|
|
|
logger.Error("reading response", zap.Error(err))
|
2023-08-16 01:40:00 +00:00
|
|
|
wakuLP.metrics.RecordError(decodeRPCFailure)
|
2023-10-22 01:34:52 +00:00
|
|
|
if err := stream.Reset(); err != nil {
|
|
|
|
wakuLP.log.Error("resetting connection", zap.Error(err))
|
|
|
|
}
|
2021-04-28 20:10:44 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2023-10-22 01:34:52 +00:00
|
|
|
stream.Close()
|
|
|
|
|
2023-10-24 16:26:02 +00:00
|
|
|
if err = pushResponseRPC.ValidateResponse(pushRequestRPC.RequestId); err != nil {
|
|
|
|
wakuLP.metrics.RecordError(responseBodyFailure)
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2021-04-28 20:10:44 +00:00
|
|
|
return pushResponseRPC.Response, nil
|
|
|
|
}
|
2021-10-11 22:45:54 +00:00
|
|
|
|
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() {
|
2023-01-06 22:37:57 +00:00
|
|
|
if wakuLP.cancel == nil {
|
|
|
|
return
|
2022-10-20 13:42:01 +00:00
|
|
|
}
|
2023-01-06 22:37:57 +00:00
|
|
|
|
|
|
|
wakuLP.cancel()
|
|
|
|
wakuLP.h.RemoveStreamHandler(LightPushID_v20beta1)
|
2021-10-11 22:45:54 +00:00
|
|
|
}
|
2021-11-01 12:38:03 +00:00
|
|
|
|
2023-10-16 16:42:01 +00:00
|
|
|
func (wakuLP *WakuLightPush) handleOpts(ctx context.Context, message *wpb.WakuMessage, opts ...Option) (*lightPushParameters, error) {
|
2023-09-29 05:13:25 +00:00
|
|
|
params := new(lightPushParameters)
|
|
|
|
params.host = wakuLP.h
|
|
|
|
params.log = wakuLP.log
|
|
|
|
params.pm = wakuLP.pm
|
2023-10-16 16:42:01 +00:00
|
|
|
var err error
|
2023-09-29 05:13:25 +00:00
|
|
|
|
|
|
|
optList := append(DefaultOptions(wakuLP.h), opts...)
|
|
|
|
for _, opt := range optList {
|
|
|
|
opt(params)
|
|
|
|
}
|
2021-11-01 12:38:03 +00:00
|
|
|
|
2023-09-29 05:13:25 +00:00
|
|
|
if params.pubsubTopic == "" {
|
|
|
|
params.pubsubTopic, err = protocol.GetPubSubTopicFromContentTopic(message.ContentTopic)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
2023-10-16 16:42:01 +00:00
|
|
|
|
|
|
|
if params.pm != nil && params.selectedPeer == "" {
|
|
|
|
params.selectedPeer, err = wakuLP.pm.SelectPeer(
|
|
|
|
peermanager.PeerSelectionCriteria{
|
|
|
|
SelectionType: params.peerSelectionType,
|
|
|
|
Proto: LightPushID_v20beta1,
|
|
|
|
PubsubTopic: params.pubsubTopic,
|
|
|
|
SpecificPeers: params.preferredPeers,
|
|
|
|
Ctx: ctx,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
}
|
|
|
|
if params.selectedPeer == "" {
|
|
|
|
if err != nil {
|
|
|
|
params.log.Error("selecting peer", zap.Error(err))
|
|
|
|
wakuLP.metrics.RecordError(peerNotFoundFailure)
|
|
|
|
return nil, ErrNoPeersAvailable
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return params, nil
|
|
|
|
}
|
|
|
|
|
2023-10-30 16:30:25 +00:00
|
|
|
// Publish is used to broadcast a WakuMessage to the pubSubTopic (which is derived from the
|
|
|
|
// contentTopic) via lightpush protocol. If auto-sharding is not to be used, then the
|
|
|
|
// `WithPubSubTopic` option should be provided to publish the message to an specific pubSubTopic
|
|
|
|
func (wakuLP *WakuLightPush) Publish(ctx context.Context, message *wpb.WakuMessage, opts ...Option) ([]byte, error) {
|
2023-10-16 16:42:01 +00:00
|
|
|
if message == nil {
|
|
|
|
return nil, errors.New("message can't be null")
|
|
|
|
}
|
|
|
|
|
|
|
|
params, err := wakuLP.handleOpts(ctx, message, opts...)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2021-11-01 12:38:03 +00:00
|
|
|
req := new(pb.PushRequest)
|
|
|
|
req.Message = message
|
2023-09-29 05:13:25 +00:00
|
|
|
req.PubsubTopic = params.pubsubTopic
|
2021-11-01 12:38:03 +00:00
|
|
|
|
2023-09-29 05:13:25 +00:00
|
|
|
response, err := wakuLP.request(ctx, req, params)
|
2021-11-01 12:38:03 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if response.IsSuccess {
|
2023-09-29 05:13:25 +00:00
|
|
|
hash := message.Hash(params.pubsubTopic)
|
2022-11-03 13:53:33 +00:00
|
|
|
wakuLP.log.Info("waku.lightpush published", logging.HexString("hash", hash))
|
2021-11-01 12:38:03 +00:00
|
|
|
return hash, nil
|
|
|
|
}
|
2023-09-11 14:24:05 +00:00
|
|
|
|
|
|
|
return nil, errors.New(response.Info)
|
2021-11-01 12:38:03 +00:00
|
|
|
}
|