2024-11-27 12:08:50 +02:00
|
|
|
package waku
|
|
|
|
|
|
|
|
|
|
import (
|
2025-02-19 09:21:59 +02:00
|
|
|
"context"
|
2025-03-26 12:41:36 +02:00
|
|
|
"encoding/csv"
|
2024-11-27 12:08:50 +02:00
|
|
|
"encoding/json"
|
2025-02-19 09:21:59 +02:00
|
|
|
"errors"
|
2024-11-27 12:08:50 +02:00
|
|
|
"fmt"
|
|
|
|
|
"io"
|
|
|
|
|
"net/http"
|
|
|
|
|
"os"
|
2025-05-14 18:53:27 +03:00
|
|
|
"runtime"
|
2024-11-27 12:08:50 +02:00
|
|
|
"strconv"
|
2025-03-26 12:41:36 +02:00
|
|
|
"sync"
|
2024-11-27 12:08:50 +02:00
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
"github.com/cenkalti/backoff/v3"
|
2025-02-26 13:47:34 +02:00
|
|
|
"github.com/libp2p/go-libp2p/core/peer"
|
2025-12-12 22:48:53 +01:00
|
|
|
"github.com/logos-messaging/logos-messaging-go-bindings/utils"
|
|
|
|
|
"github.com/logos-messaging/logos-messaging-go-bindings/waku/common"
|
2025-12-16 03:06:15 +05:30
|
|
|
"github.com/logos-messaging/logos-messaging-go/v2/protocol/pb"
|
2025-02-19 09:21:59 +02:00
|
|
|
"google.golang.org/protobuf/proto"
|
2024-11-27 12:08:50 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type NwakuInfo struct {
|
|
|
|
|
ListenAddresses []string `json:"listenAddresses"`
|
|
|
|
|
EnrUri string `json:"enrUri"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func GetNwakuInfo(host *string, port *int) (NwakuInfo, error) {
|
|
|
|
|
nwakuRestPort := 8645
|
|
|
|
|
if port != nil {
|
|
|
|
|
nwakuRestPort = *port
|
|
|
|
|
}
|
|
|
|
|
envNwakuRestPort := os.Getenv("NWAKU_REST_PORT")
|
|
|
|
|
if envNwakuRestPort != "" {
|
|
|
|
|
v, err := strconv.Atoi(envNwakuRestPort)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return NwakuInfo{}, err
|
|
|
|
|
}
|
|
|
|
|
nwakuRestPort = v
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
nwakuRestHost := "localhost"
|
|
|
|
|
if host != nil {
|
|
|
|
|
nwakuRestHost = *host
|
|
|
|
|
}
|
|
|
|
|
envNwakuRestHost := os.Getenv("NWAKU_REST_HOST")
|
|
|
|
|
if envNwakuRestHost != "" {
|
|
|
|
|
nwakuRestHost = envNwakuRestHost
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
resp, err := http.Get(fmt.Sprintf("http://%s:%d/debug/v1/info", nwakuRestHost, nwakuRestPort))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return NwakuInfo{}, err
|
|
|
|
|
}
|
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
|
|
|
|
|
body, err := io.ReadAll(resp.Body)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return NwakuInfo{}, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var data NwakuInfo
|
|
|
|
|
err = json.Unmarshal(body, &data)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return NwakuInfo{}, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return data, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type BackOffOption func(*backoff.ExponentialBackOff)
|
|
|
|
|
|
|
|
|
|
func RetryWithBackOff(o func() error, options ...BackOffOption) error {
|
|
|
|
|
b := backoff.ExponentialBackOff{
|
|
|
|
|
InitialInterval: time.Millisecond * 100,
|
|
|
|
|
RandomizationFactor: 0.1,
|
|
|
|
|
Multiplier: 1,
|
|
|
|
|
MaxInterval: time.Second,
|
|
|
|
|
MaxElapsedTime: time.Second * 10,
|
|
|
|
|
Clock: backoff.SystemClock,
|
|
|
|
|
}
|
|
|
|
|
for _, option := range options {
|
|
|
|
|
option(&b)
|
|
|
|
|
}
|
|
|
|
|
b.Reset()
|
|
|
|
|
return backoff.Retry(o, &b)
|
|
|
|
|
}
|
2025-02-19 09:21:59 +02:00
|
|
|
|
|
|
|
|
func (n *WakuNode) CreateMessage(customMessage ...*pb.WakuMessage) *pb.WakuMessage {
|
|
|
|
|
Debug("Creating a WakuMessage on node %s", n.nodeName)
|
|
|
|
|
|
|
|
|
|
if len(customMessage) > 0 && customMessage[0] != nil {
|
|
|
|
|
Debug("Using provided custom message on node %s", n.nodeName)
|
|
|
|
|
return customMessage[0]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Debug("Using default message format on node %s", n.nodeName)
|
|
|
|
|
defaultMessage := &pb.WakuMessage{
|
|
|
|
|
Payload: []byte("This is a default Waku message payload"),
|
2025-03-02 16:44:30 +02:00
|
|
|
ContentTopic: DefaultContentTopic,
|
2025-02-19 09:21:59 +02:00
|
|
|
Version: proto.Uint32(0),
|
|
|
|
|
Timestamp: proto.Int64(time.Now().UnixNano()),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Debug("Successfully created a default WakuMessage on node %s", n.nodeName)
|
|
|
|
|
return defaultMessage
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func WaitForAutoConnection(nodeList []*WakuNode) error {
|
|
|
|
|
Debug("Waiting for auto-connection of nodes...")
|
|
|
|
|
|
|
|
|
|
options := func(b *backoff.ExponentialBackOff) {
|
|
|
|
|
b.MaxElapsedTime = 30 * time.Second
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err := RetryWithBackOff(func() error {
|
|
|
|
|
for _, node := range nodeList {
|
|
|
|
|
peers, err := node.GetConnectedPeers()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(peers) < 1 {
|
|
|
|
|
return errors.New("expected at least one connected peer") // Retry
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Debug("Node %s has %d connected peers", node.nodeName, len(peers))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}, options)
|
|
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
Error("Auto-connection failed after retries: %v", err)
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Debug("Auto-connection check completed successfully")
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (n *WakuNode) VerifyMessageReceived(expectedMessage *pb.WakuMessage, expectedHash common.MessageHash, timeout ...time.Duration) error {
|
|
|
|
|
|
|
|
|
|
var verifyTimeout time.Duration
|
|
|
|
|
if len(timeout) > 0 {
|
|
|
|
|
verifyTimeout = timeout[0]
|
|
|
|
|
} else {
|
|
|
|
|
verifyTimeout = DefaultTimeOut
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Debug("Verifying if the message was received on node %s, timeout: %v", n.nodeName, verifyTimeout)
|
|
|
|
|
|
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), verifyTimeout)
|
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
|
|
select {
|
|
|
|
|
case envelope := <-n.MsgChan:
|
|
|
|
|
if string(expectedMessage.Payload) != string(envelope.Message().Payload) {
|
|
|
|
|
Error("Payload does not match on node %s", n.nodeName)
|
|
|
|
|
return errors.New("payload does not match")
|
|
|
|
|
}
|
|
|
|
|
if expectedMessage.ContentTopic != envelope.Message().ContentTopic {
|
|
|
|
|
Error("Content topic does not match on node %s", n.nodeName)
|
|
|
|
|
return errors.New("content topic does not match")
|
|
|
|
|
}
|
|
|
|
|
if expectedHash != envelope.Hash() {
|
|
|
|
|
Error("Message hash does not match on node %s", n.nodeName)
|
|
|
|
|
return errors.New("message hash does not match")
|
|
|
|
|
}
|
|
|
|
|
Debug("Message received and verified successfully on node %s, Message: %s", n.nodeName, string(envelope.Message().Payload))
|
|
|
|
|
return nil
|
|
|
|
|
case <-ctx.Done():
|
|
|
|
|
Error("Timeout: message not received within %v on node %s", verifyTimeout, n.nodeName)
|
|
|
|
|
return errors.New("timeout: message not received within the given duration")
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-02-26 12:52:30 +02:00
|
|
|
|
|
|
|
|
func ConnectAllPeers(nodes []*WakuNode) error {
|
|
|
|
|
if len(nodes) == 0 {
|
|
|
|
|
Error("Cannot connect peers: node list is empty")
|
|
|
|
|
return errors.New("node list is empty")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
timeout := time.Duration(len(nodes)*2) * time.Second
|
|
|
|
|
Debug("Connecting nodes in a relay chain with timeout: %v", timeout)
|
|
|
|
|
|
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
|
|
for i := 0; i < len(nodes)-1; i++ {
|
|
|
|
|
Debug("Connecting node %d to node %d", i, i+1)
|
|
|
|
|
err := nodes[i].ConnectPeer(nodes[i+1])
|
|
|
|
|
if err != nil {
|
|
|
|
|
Error("Failed to connect node %d to node %d: %v", i, i+1, err)
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
<-ctx.Done()
|
|
|
|
|
Debug("Connections stabilized")
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func SubscribeNodesToTopic(nodes []*WakuNode, topic string) error {
|
|
|
|
|
for _, node := range nodes {
|
|
|
|
|
Debug("Subscribing node %s to topic %s", node.nodeName, topic)
|
|
|
|
|
err := node.RelaySubscribe(topic)
|
|
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
Error("Failed to subscribe node %s to topic %s: %v", node.nodeName, topic, err)
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
Debug("Node %s successfully subscribed to topic %s", node.nodeName, topic)
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
2025-02-26 13:47:34 +02:00
|
|
|
|
|
|
|
|
func (n *WakuNode) GetStoredMessages(storeNode *WakuNode, storeRequest *common.StoreQueryRequest) (*common.StoreQueryResponse, error) {
|
|
|
|
|
Debug("Starting store query request")
|
|
|
|
|
|
|
|
|
|
if storeRequest == nil {
|
|
|
|
|
Debug("Using DefaultStoreQueryRequest")
|
|
|
|
|
storeRequest = &DefaultStoreQueryRequest
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
storeMultiaddr, err := storeNode.ListenAddresses()
|
|
|
|
|
if err != nil {
|
|
|
|
|
Error("Failed to retrieve listen addresses for store node: %v", err)
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(storeMultiaddr) == 0 {
|
|
|
|
|
Error("Store node has no available listen addresses")
|
2025-03-06 15:03:19 +02:00
|
|
|
return nil, errors.New("store node has no available listen addresses")
|
2025-02-26 13:47:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
storeNodeAddrInfo, err := peer.AddrInfoFromString(storeMultiaddr[0].String())
|
|
|
|
|
if err != nil {
|
|
|
|
|
Error("Failed to convert store node address to AddrInfo: %v", err)
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
|
|
Debug("Querying store node for messages")
|
|
|
|
|
res, err := n.StoreQuery(ctx, storeRequest, *storeNodeAddrInfo)
|
|
|
|
|
if err != nil {
|
|
|
|
|
Error("StoreQuery failed: %v", err)
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Debug("Store query successful, retrieved %d messages", len(*res.Messages))
|
|
|
|
|
return res, nil
|
|
|
|
|
}
|
2025-03-26 12:41:36 +02:00
|
|
|
|
|
|
|
|
func recordMemoryMetricsPX(testName, phase string, heapAllocKB, rssKB uint64) error {
|
|
|
|
|
staticMu := sync.Mutex{}
|
|
|
|
|
staticMu.Lock()
|
|
|
|
|
defer staticMu.Unlock()
|
|
|
|
|
|
|
|
|
|
file, err := os.OpenFile("px_load_metrics.csv", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
defer file.Close()
|
|
|
|
|
|
|
|
|
|
writer := csv.NewWriter(file)
|
|
|
|
|
defer writer.Flush()
|
|
|
|
|
|
|
|
|
|
stat, err := file.Stat()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if stat.Size() == 0 {
|
|
|
|
|
header := []string{"TestName", "Phase", "HeapAlloc(KB)", "RSS(KB)", "Timestamp"}
|
|
|
|
|
if err := writer.Write(header); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
row := []string{
|
|
|
|
|
testName,
|
|
|
|
|
phase,
|
|
|
|
|
strconv.FormatUint(heapAllocKB, 10),
|
|
|
|
|
strconv.FormatUint(rssKB, 10),
|
|
|
|
|
time.Now().Format(time.RFC3339),
|
|
|
|
|
}
|
|
|
|
|
return writer.Write(row)
|
|
|
|
|
}
|
2025-05-14 18:53:27 +03:00
|
|
|
|
|
|
|
|
func captureMemory(testName, phase string) {
|
|
|
|
|
var ms runtime.MemStats
|
|
|
|
|
runtime.ReadMemStats(&ms)
|
|
|
|
|
|
|
|
|
|
heapKB := ms.HeapAlloc / 1024
|
2025-12-12 22:48:53 +01:00
|
|
|
rssKB, _ := utils.GetRSSKB()
|
2025-05-14 18:53:27 +03:00
|
|
|
|
|
|
|
|
Debug("[%s] Memory usage (%s): %d KB (RSS %d KB)", testName, phase, heapKB, rssKB)
|
|
|
|
|
|
|
|
|
|
_ = recordMemoryMetricsPX(testName, phase, heapKB, rssKB)
|
2025-12-12 22:48:53 +01:00
|
|
|
}
|