diff --git a/Makefile b/Makefile index 0d20d887..fe4536d9 100644 --- a/Makefile +++ b/Makefile @@ -61,11 +61,16 @@ CONFIG_FILE3=tests/system/config.test-3.yml SETUP_SCRIPT=tests/scripts/setup-supernodes.sh # Install Lumera +# Optional: specify lumera binary path to skip download +LUMERAD_BINARY ?= +# Optional: specify installation mode (latest-release, latest-tag, or vX.Y.Z) +INSTALL_MODE ?=latest-tag + install-lumera: @echo "Installing Lumera..." @chmod +x tests/scripts/install-lumera.sh - @sudo tests/scripts/install-lumera.sh latest-tag - + @sudo LUMERAD_BINARY="$(LUMERAD_BINARY)" tests/scripts/install-lumera.sh $(INSTALL_MODE) + @echo "PtTDUHythfRfXHh63yzyiGDid4TZj2P76Zd,18749999981413" > ~/claims.csv # Setup supernode environments setup-supernodes: @echo "Setting up all supernode environments..." diff --git a/go.mod b/go.mod index 7292b8e3..abad780a 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ replace github.com/LumeraProtocol/supernode/supernode => ./supernode require ( cosmossdk.io/math v1.5.3 - github.com/LumeraProtocol/lumera v1.5.0 + github.com/LumeraProtocol/lumera v1.6.0 github.com/LumeraProtocol/rq-go v0.2.1 github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce github.com/cenkalti/backoff/v4 v4.3.0 @@ -28,11 +28,9 @@ require ( github.com/patrickmn/go-cache v2.1.0+incompatible github.com/pkg/errors v0.9.1 github.com/shirou/gopsutil/v3 v3.24.5 - github.com/sirupsen/logrus v1.9.3 github.com/spf13/cobra v1.8.1 github.com/spf13/viper v1.19.0 github.com/stretchr/testify v1.10.0 - github.com/x-cray/logrus-prefixed-formatter v0.5.2 go.uber.org/mock v0.5.2 go.uber.org/ratelimit v0.3.1 golang.org/x/crypto v0.36.0 @@ -40,7 +38,6 @@ require ( golang.org/x/sys v0.31.0 google.golang.org/grpc v1.71.0 google.golang.org/protobuf v1.36.6 - gopkg.in/natefinch/lumberjack.v2 v2.2.1 gopkg.in/yaml.v3 v3.0.1 lukechampine.com/blake3 v1.4.0 ) @@ -134,7 +131,6 @@ require ( github.com/magiconair/properties v1.8.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect github.com/mitchellh/go-testing-interface v1.14.1 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect diff --git a/go.sum b/go.sum index 0d2e187a..498582a4 100644 --- a/go.sum +++ b/go.sum @@ -61,8 +61,8 @@ github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3 github.com/DataDog/zstd v1.5.5 h1:oWf5W7GtOLgp6bciQYDmhHHjdhYkALu6S/5Ni9ZgSvQ= github.com/DataDog/zstd v1.5.5/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= -github.com/LumeraProtocol/lumera v1.5.0 h1:LDPtd155PjG/LKk34x/3vhC9H+J9tHoxwrcwRMG6jzM= -github.com/LumeraProtocol/lumera v1.5.0/go.mod h1:c1M+sjewuCvxw+pznwlspUzenDJI8Y+suKB3RFKS2Wo= +github.com/LumeraProtocol/lumera v1.6.0 h1:5I172U/f1Migt7tRxnywhz5aRKCpBOx/IMgOzhJfTP0= +github.com/LumeraProtocol/lumera v1.6.0/go.mod h1:c1M+sjewuCvxw+pznwlspUzenDJI8Y+suKB3RFKS2Wo= github.com/LumeraProtocol/rq-go v0.2.1 h1:8B3UzRChLsGMmvZ+UVbJsJj6JZzL9P9iYxbdUwGsQI4= github.com/LumeraProtocol/rq-go v0.2.1/go.mod h1:APnKCZRh1Es2Vtrd2w4kCLgAyaL5Bqrkz/BURoRJ+O8= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= @@ -558,8 +558,6 @@ github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxU github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= -github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/minio/highwayhash v1.0.3 h1:kbnuUMoHYyVl7szWjSxJnxw11k2U709jqFPPmIUyD6Q= github.com/minio/highwayhash v1.0.3/go.mod h1:GGYsuwP/fPD6Y9hMiXuapVvlIUEhFhMTh0rxU3ik1LQ= @@ -733,8 +731,6 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= @@ -798,8 +794,6 @@ github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8= github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/x-cray/logrus-prefixed-formatter v0.5.2 h1:00txxvfBM9muc0jiLIEAkAcIMJzfthRT6usrui8uGmg= -github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -987,7 +981,6 @@ golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1110,8 +1103,6 @@ gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMy gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= -gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= diff --git a/p2p/kademlia/bootstrap.go b/p2p/kademlia/bootstrap.go index 6dcd71c2..8ce18f10 100644 --- a/p2p/kademlia/bootstrap.go +++ b/p2p/kademlia/bootstrap.go @@ -3,7 +3,6 @@ package kademlia import ( "context" "fmt" - "os" "strconv" "strings" "sync" @@ -34,11 +33,6 @@ func (s *DHT) parseNode(extP2P string, selfAddr string) (*Node, error) { return nil, errors.New("empty address") } - /*if strings.Contains(extP2P, "0.0.0.0") { - fmt.Println("skippping node") - return nil, errors.New("invalid address") - }*/ - if extP2P == selfAddr { return nil, errors.New("self address") } @@ -61,24 +55,12 @@ func (s *DHT) parseNode(extP2P string, selfAddr string) (*Node, error) { if err != nil { return nil, errors.New("invalid port number") } - - // For system testing, use port+1 if SYSTEM_TEST=true - if os.Getenv("SYSTEM_TEST") == "true" { - port = uint16(portNum) + 1 - logtrace.Info(context.Background(), "Using port+1 for system testing", logtrace.Fields{ - logtrace.FieldModule: "p2p", - "original_port": portNum, - "adjusted_port": port, - }) - } else { - // For normal P2P operation, always use the default port - port = defaultNetworkPort - } + port = uint16(portNum) } } else { // No port in the address ip = extP2P - port = defaultNetworkPort + port = defaultSuperNodeP2PPort } if ip == "" { @@ -170,13 +152,32 @@ func (s *DHT) ConfigureBootstrapNodes(ctx context.Context, bootstrapNodes string continue } - // Parse the node from the IP address - node, err := s.parseNode(latestIP, selfAddress) + // Extract IP from the address (remove port if present) + var ip string + if idx := strings.LastIndex(latestIP, ":"); idx != -1 { + ip = latestIP[:idx] + } else { + ip = latestIP + } + + // Use p2p_port from supernode record + p2pPort := defaultSuperNodeP2PPort + if supernode.P2PPort != "" { + if port, err := strconv.ParseUint(supernode.P2PPort, 10, 16); err == nil { + p2pPort = int(port) + } + } + + // Create full address with p2p port for validation + fullAddress := fmt.Sprintf("%s:%d", ip, p2pPort) + + // Parse the node from the full address + node, err := s.parseNode(fullAddress, selfAddress) if err != nil { logtrace.Warn(ctx, "Skip Bad Bootstrap Address", logtrace.Fields{ logtrace.FieldModule: "p2p", logtrace.FieldError: err.Error(), - "address": latestIP, + "address": fullAddress, "supernode": supernode.SupernodeAccount, }) continue @@ -184,7 +185,7 @@ func (s *DHT) ConfigureBootstrapNodes(ctx context.Context, bootstrapNodes string // Store the supernode account as the node ID node.ID = []byte(supernode.SupernodeAccount) - mapNodes[latestIP] = node + mapNodes[fullAddress] = node } // Convert the map to a slice diff --git a/sdk/action/client.go b/sdk/action/client.go index ca19ec57..6bb3ed0a 100644 --- a/sdk/action/client.go +++ b/sdk/action/client.go @@ -4,9 +4,12 @@ import ( "context" "fmt" + "github.com/LumeraProtocol/supernode/sdk/adapters/lumera" + "github.com/LumeraProtocol/supernode/sdk/adapters/supernodeservice" "github.com/LumeraProtocol/supernode/sdk/config" "github.com/LumeraProtocol/supernode/sdk/event" "github.com/LumeraProtocol/supernode/sdk/log" + "github.com/LumeraProtocol/supernode/sdk/net" "github.com/LumeraProtocol/supernode/sdk/task" "github.com/cosmos/cosmos-sdk/crypto/keyring" @@ -16,24 +19,25 @@ import ( // //go:generate mockery --name=Client --output=testutil/mocks --outpkg=mocks --filename=client_mock.go type Client interface { - // - signature: Base64-encoded cryptographic signature of the file's data hash (blake3) - // 1- hash(blake3) > 2- sign > 3- base64 - // The signature must be created by the same account that created the Lumera action. - // It must be a digital signature of the data hash found in the action's CASCADE metadata. + // StartCascade initiates a cascade operation with file path, action ID, and signature + // signature: Base64-encoded signature of file's blake3 hash by action creator StartCascade(ctx context.Context, filePath string, actionID string, signature string) (string, error) DeleteTask(ctx context.Context, taskID string) error GetTask(ctx context.Context, taskID string) (*task.TaskEntry, bool) SubscribeToEvents(ctx context.Context, eventType event.EventType, handler event.Handler) error SubscribeToAllEvents(ctx context.Context, handler event.Handler) error - DownloadCascade(ctx context.Context, actionID, outputPath string) (string, error) + GetSupernodeStatus(ctx context.Context, supernodeAddress string) (*supernodeservice.SupernodeStatusresponse, error) + // DownloadCascade downloads cascade to outputDir, filename determined by action ID + DownloadCascade(ctx context.Context, actionID, outputDir string) (string, error) } // ClientImpl implements the Client interface type ClientImpl struct { - config config.Config - taskManager task.Manager - logger log.Logger - keyring keyring.Keyring + config config.Config + taskManager task.Manager + logger log.Logger + keyring keyring.Keyring + lumeraClient lumera.Client } // Verify interface compliance at compile time @@ -45,15 +49,31 @@ func NewClient(ctx context.Context, config config.Config, logger log.Logger) (Cl logger = log.NewNoopLogger() } - taskManager, err := task.NewManager(ctx, config, logger) + // Create lumera client once + lumeraClient, err := lumera.NewAdapter(ctx, + lumera.ConfigParams{ + GRPCAddr: config.Lumera.GRPCAddr, + ChainID: config.Lumera.ChainID, + KeyName: config.Account.KeyName, + Keyring: config.Account.Keyring, + }, + logger) + if err != nil { + return nil, fmt.Errorf("failed to create lumera client: %w", err) + } + + // Create task manager with shared lumera client + taskManager, err := task.NewManagerWithLumeraClient(ctx, config, logger, lumeraClient) if err != nil { return nil, fmt.Errorf("failed to create task manager: %w", err) } return &ClientImpl{ - config: config, - taskManager: taskManager, - logger: logger, + config: config, + taskManager: taskManager, + logger: logger, + keyring: config.Account.Keyring, + lumeraClient: lumeraClient, }, nil } @@ -130,16 +150,68 @@ func (c *ClientImpl) SubscribeToAllEvents(ctx context.Context, handler event.Han return nil } -func (c *ClientImpl) DownloadCascade( - ctx context.Context, - actionID, outputPath string, -) (string, error) { +// GetSupernodeStatus retrieves the status of a specific supernode by its address +func (c *ClientImpl) GetSupernodeStatus(ctx context.Context, supernodeAddress string) (*supernodeservice.SupernodeStatusresponse, error) { + if supernodeAddress == "" { + c.logger.Error(ctx, "Empty supernode address provided") + return nil, fmt.Errorf("supernode address cannot be empty") + } + + c.logger.Debug(ctx, "Getting supernode status", "address", supernodeAddress) + + // Get supernode details from blockchain + supernode, err := c.lumeraClient.GetSupernodeBySupernodeAddress(ctx, supernodeAddress) + if err != nil { + c.logger.Error(ctx, "Failed to get supernode details", "address", supernodeAddress, "error", err) + return nil, fmt.Errorf("failed to get supernode details: %w", err) + } + + // Get the latest IP address for the supernode + if len(supernode.PrevIpAddresses) == 0 { + return nil, fmt.Errorf("no IP addresses found for supernode %s", supernodeAddress) + } + + ipAddress := supernode.PrevIpAddresses[0].Address + + // Create lumera supernode object for network client + lumeraSupernode := lumera.Supernode{ + CosmosAddress: supernodeAddress, + GrpcEndpoint: ipAddress, + State: lumera.SUPERNODE_STATE_ACTIVE, // Assume active since we're querying + } + + // Create network client factory + clientFactory := net.NewClientFactory(ctx, c.logger, c.keyring, c.lumeraClient, net.FactoryConfig{ + LocalCosmosAddress: c.config.Account.LocalCosmosAddress, + PeerType: c.config.Account.PeerType, + }) + + // Create client for the specific supernode + supernodeClient, err := clientFactory.CreateClient(ctx, lumeraSupernode) + if err != nil { + c.logger.Error(ctx, "Failed to create supernode client", "address", supernodeAddress, "error", err) + return nil, fmt.Errorf("failed to create supernode client: %w", err) + } + defer supernodeClient.Close(ctx) + + // Get the supernode status + status, err := supernodeClient.GetSupernodeStatus(ctx) + if err != nil { + c.logger.Error(ctx, "Failed to get supernode status", "address", supernodeAddress, "error", err) + return nil, fmt.Errorf("failed to get supernode status: %w", err) + } + + c.logger.Info(ctx, "Successfully retrieved supernode status", "address", supernodeAddress) + return status, nil +} + +func (c *ClientImpl) DownloadCascade(ctx context.Context, actionID, outputDir string) (string, error) { if actionID == "" { return "", fmt.Errorf("actionID is empty") } - taskID, err := c.taskManager.CreateDownloadTask(ctx, actionID, outputPath) + taskID, err := c.taskManager.CreateDownloadTask(ctx, actionID, outputDir) if err != nil { return "", fmt.Errorf("create download task: %w", err) } diff --git a/sdk/adapters/lumera/adapter.go b/sdk/adapters/lumera/adapter.go index 126adbe2..8da42756 100644 --- a/sdk/adapters/lumera/adapter.go +++ b/sdk/adapters/lumera/adapter.go @@ -9,9 +9,9 @@ import ( actiontypes "github.com/LumeraProtocol/lumera/x/action/v1/types" sntypes "github.com/LumeraProtocol/lumera/x/supernode/v1/types" - authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" lumeraclient "github.com/LumeraProtocol/supernode/pkg/lumera" "github.com/cosmos/cosmos-sdk/crypto/keyring" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" "github.com/golang/protobuf/proto" ) diff --git a/sdk/adapters/supernodeservice/adapter.go b/sdk/adapters/supernodeservice/adapter.go index 7b4cc7a4..f88e2129 100644 --- a/sdk/adapters/supernodeservice/adapter.go +++ b/sdk/adapters/supernodeservice/adapter.go @@ -5,6 +5,7 @@ import ( "fmt" "io" "os" + "path/filepath" "github.com/LumeraProtocol/supernode/gen/supernode/action/cascade" "github.com/LumeraProtocol/supernode/pkg/net" @@ -164,6 +165,18 @@ func (a *cascadeAdapter) CascadeSupernodeRegister(ctx context.Context, in *Casca }, nil } +func (a *cascadeAdapter) GetSupernodeStatus(ctx context.Context) (SupernodeStatusresponse, error) { + resp, err := a.client.HealthCheck(ctx, &cascade.HealthCheckRequest{}) + if err != nil { + a.logger.Error(ctx, "Failed to get supernode status", "error", err) + return SupernodeStatusresponse{}, fmt.Errorf("failed to get supernode status: %w", err) + } + + a.logger.Debug(ctx, "Supernode status retrieved", "status", resp) + + return *toSdkSupernodeStatus(resp), nil +} + // CascadeSupernodeDownload downloads a file from a supernode gRPC stream func (a *cascadeAdapter) CascadeSupernodeDownload( ctx context.Context, @@ -178,16 +191,20 @@ func (a *cascadeAdapter) CascadeSupernodeDownload( ActionId: in.ActionID, }, opts...) if err != nil { - a.logger.Error(ctx, "failed to create download stream", - "action_id", in.ActionID, "error", err) + a.logger.Error(ctx, "failed to create download stream", "action_id", in.ActionID, "error", err) return nil, err } // 2. Prepare destination file + // Create directory structure if it doesn't exist + if err := os.MkdirAll(filepath.Dir(in.OutputPath), 0755); err != nil { + a.logger.Error(ctx, "failed to create output directory", "path", filepath.Dir(in.OutputPath), "error", err) + return nil, fmt.Errorf("create output directory: %w", err) + } + outFile, err := os.Create(in.OutputPath) if err != nil { - a.logger.Error(ctx, "failed to create output file", - "path", in.OutputPath, "error", err) + a.logger.Error(ctx, "failed to create output file", "path", in.OutputPath, "error", err) return nil, fmt.Errorf("create output file: %w", err) } defer outFile.Close() @@ -211,10 +228,7 @@ func (a *cascadeAdapter) CascadeSupernodeDownload( // 3a. Progress / event message case *cascade.DownloadResponse_Event: - a.logger.Info(ctx, "supernode event", - "event_type", x.Event.EventType, - "message", x.Event.Message, - "action_id", in.ActionID) + a.logger.Info(ctx, "supernode event", "event_type", x.Event.EventType, "message", x.Event.Message, "action_id", in.ActionID) if in.EventLogger != nil { in.EventLogger(ctx, toSdkEvent(x.Event.EventType), x.Event.Message, event.EventData{ @@ -237,17 +251,11 @@ func (a *cascadeAdapter) CascadeSupernodeDownload( bytesWritten += int64(len(data)) chunkIndex++ - a.logger.Debug(ctx, "received chunk", - "chunk_index", chunkIndex, - "chunk_size", len(data), - "bytes_written", bytesWritten) + a.logger.Debug(ctx, "received chunk", "chunk_index", chunkIndex, "chunk_size", len(data), "bytes_written", bytesWritten) } } - a.logger.Info(ctx, "download complete", - "bytes_written", bytesWritten, - "path", in.OutputPath, - "action_id", in.ActionID) + a.logger.Info(ctx, "download complete", "bytes_written", bytesWritten, "path", in.OutputPath, "action_id", in.ActionID) return &CascadeSupernodeDownloadResponse{ Success: true, @@ -287,3 +295,25 @@ func toSdkEvent(e cascade.SupernodeEventType) event.EventType { return event.SupernodeUnknown } } + +func toSdkSupernodeStatus(resp *cascade.HealthCheckResponse) *SupernodeStatusresponse { + result := &SupernodeStatusresponse{ + TasksInProgress: resp.TasksInProgress, + } + + // Convert CPU data + if resp.Cpu != nil { + result.CPU.Usage = resp.Cpu.Usage + result.CPU.Remaining = resp.Cpu.Remaining + } + + // Convert Memory data + if resp.Memory != nil { + result.Memory.Total = resp.Memory.Total + result.Memory.Used = resp.Memory.Used + result.Memory.Available = resp.Memory.Available + result.Memory.UsedPerc = resp.Memory.UsedPerc + } + + return result +} diff --git a/sdk/adapters/supernodeservice/types.go b/sdk/adapters/supernodeservice/types.go index f78decf9..90c42040 100644 --- a/sdk/adapters/supernodeservice/types.go +++ b/sdk/adapters/supernodeservice/types.go @@ -28,6 +28,19 @@ type CascadeSupernodeRegisterResponse struct { TxHash string } +type SupernodeStatusresponse struct { + CPU struct { + Usage string + Remaining string + } + Memory struct { + Total uint64 + Used uint64 + Available uint64 + UsedPerc float64 + } + TasksInProgress []string +} type CascadeSupernodeDownloadRequest struct { ActionID string TaskID string @@ -44,5 +57,6 @@ type CascadeSupernodeDownloadResponse struct { //go:generate mockery --name=CascadeServiceClient --output=testutil/mocks --outpkg=mocks --filename=cascade_service_mock.go type CascadeServiceClient interface { CascadeSupernodeRegister(ctx context.Context, in *CascadeSupernodeRegisterRequest, opts ...grpc.CallOption) (*CascadeSupernodeRegisterResponse, error) + GetSupernodeStatus(ctx context.Context) (SupernodeStatusresponse, error) CascadeSupernodeDownload(ctx context.Context, in *CascadeSupernodeDownloadRequest, opts ...grpc.CallOption) (*CascadeSupernodeDownloadResponse, error) } diff --git a/sdk/net/client.go b/sdk/net/client.go index ee058d84..6366bc5e 100644 --- a/sdk/net/client.go +++ b/sdk/net/client.go @@ -15,6 +15,7 @@ type SupernodeClient interface { // HealthCheck performs a health check on the supernode HealthCheck(ctx context.Context) (*grpc_health_v1.HealthCheckResponse, error) + GetSupernodeStatus(ctx context.Context) (*supernodeservice.SupernodeStatusresponse, error) // Download downloads the cascade action file Download(ctx context.Context, in *supernodeservice.CascadeSupernodeDownloadRequest, opts ...grpc.CallOption) (*supernodeservice.CascadeSupernodeDownloadResponse, error) diff --git a/sdk/net/impl.go b/sdk/net/impl.go index bccfc367..68d6e226 100644 --- a/sdk/net/impl.go +++ b/sdk/net/impl.go @@ -129,6 +129,16 @@ func (c *supernodeClient) HealthCheck(ctx context.Context) (*grpc_health_v1.Heal return resp, nil } +func (c *supernodeClient) GetSupernodeStatus(ctx context.Context) (*supernodeservice.SupernodeStatusresponse, error) { + resp, err := c.cascadeClient.GetSupernodeStatus(ctx) + if err != nil { + return nil, fmt.Errorf("failed to get supernode status: %w", err) + } + + c.logger.Debug(ctx, "Supernode status retrieved successfully") + return &resp, nil +} + // Download downloads the cascade action file func (c *supernodeClient) Download(ctx context.Context, in *supernodeservice.CascadeSupernodeDownloadRequest, opts ...grpc.CallOption) (*supernodeservice.CascadeSupernodeDownloadResponse, error) { resp, err := c.cascadeClient.CascadeSupernodeDownload(ctx, in, opts...) diff --git a/sdk/task/download.go b/sdk/task/download.go index 36b99c44..147ea9f0 100644 --- a/sdk/task/download.go +++ b/sdk/task/download.go @@ -65,33 +65,42 @@ func (t *CascadeDownloadTask) downloadFromSupernodes(ctx context.Context, supern OutputPath: t.outputPath, } - var lastErr error - for idx, sn := range supernodes { - t.LogEvent(ctx, event.SDKDownloadAttempt, "attempting download from super-node", event.EventData{ - event.KeySupernode: sn.GrpcEndpoint, - event.KeySupernodeAddress: sn.CosmosAddress, - event.KeyIteration: idx + 1, - }) + // Process supernodes in pairs + var allErrors []error + for i := 0; i < len(supernodes); i += 2 { + // Determine how many supernodes to try in this batch (1 or 2) + batchSize := 2 + if i+1 >= len(supernodes) { + batchSize = 1 + } - if err := t.attemptDownload(ctx, sn, clientFactory, req); err != nil { - lastErr = err - t.LogEvent(ctx, event.SDKDownloadFailure, "download from super-node failed", event.EventData{ - event.KeySupernode: sn.GrpcEndpoint, - event.KeySupernodeAddress: sn.CosmosAddress, - event.KeyIteration: idx + 1, - event.KeyError: err.Error(), + t.logger.Info(ctx, "attempting concurrent download from supernode batch", "batch_start", i, "batch_size", batchSize) + + // Try downloading from this batch concurrently + result, batchErrors := t.attemptConcurrentDownload(ctx, supernodes[i:i+batchSize], clientFactory, req, i) + + if result != nil { + // Success! Log and return + t.LogEvent(ctx, event.SDKDownloadSuccessful, "download successful", event.EventData{ + event.KeySupernode: result.SupernodeEndpoint, + event.KeySupernodeAddress: result.SupernodeAddress, + event.KeyIteration: result.Iteration, }) - continue + return nil } - t.LogEvent(ctx, event.SDKDownloadSuccessful, "download successful", event.EventData{ - event.KeySupernode: sn.GrpcEndpoint, - event.KeySupernodeAddress: sn.CosmosAddress, - event.KeyIteration: idx + 1, - }) - return nil + // Both (or the single one) failed, collect errors + allErrors = append(allErrors, batchErrors...) + + // Log batch failure + t.logger.Warn(ctx, "download batch failed", "batch_start", i, "batch_size", batchSize, "errors", len(batchErrors)) + } + + // All attempts failed + if len(allErrors) > 0 { + return fmt.Errorf("failed to download from all super-nodes: %v", allErrors) } - return fmt.Errorf("failed to download from all super-nodes: %w", lastErr) + return fmt.Errorf("no supernodes available for download") } func (t *CascadeDownloadTask) attemptDownload( @@ -129,3 +138,106 @@ func (t *CascadeDownloadTask) attemptDownload( return nil } + +// downloadResult holds the result of a successful download attempt +type downloadResult struct { + SupernodeAddress string + SupernodeEndpoint string + Iteration int +} + +// attemptConcurrentDownload tries to download from multiple supernodes concurrently +// Returns the first successful result or all errors if all attempts fail +func (t *CascadeDownloadTask) attemptConcurrentDownload( + ctx context.Context, + batch lumera.Supernodes, + factory *net.ClientFactory, + req *supernodeservice.CascadeSupernodeDownloadRequest, + baseIteration int, +) (*downloadResult, []error) { + // Create a cancellable context for this batch + batchCtx, cancelBatch := context.WithCancel(ctx) + defer cancelBatch() + + // Channels for results + type attemptResult struct { + success *downloadResult + err error + idx int + } + resultCh := make(chan attemptResult, len(batch)) + + // Start concurrent download attempts + for idx, sn := range batch { + iteration := baseIteration + idx + 1 + + // Log download attempt + t.LogEvent(ctx, event.SDKDownloadAttempt, "attempting download from super-node", event.EventData{ + event.KeySupernode: sn.GrpcEndpoint, + event.KeySupernodeAddress: sn.CosmosAddress, + event.KeyIteration: iteration, + }) + + go func(sn lumera.Supernode, idx int, iter int) { + // Create a copy of the request for this goroutine + reqCopy := &supernodeservice.CascadeSupernodeDownloadRequest{ + ActionID: req.ActionID, + TaskID: req.TaskID, + OutputPath: req.OutputPath, + } + + err := t.attemptDownload(batchCtx, sn, factory, reqCopy) + if err != nil { + resultCh <- attemptResult{ + err: err, + idx: idx, + } + return + } + + resultCh <- attemptResult{ + success: &downloadResult{ + SupernodeAddress: sn.CosmosAddress, + SupernodeEndpoint: sn.GrpcEndpoint, + Iteration: iter, + }, + idx: idx, + } + }(sn, idx, iteration) + } + + // Collect results + var errors []error + for i := 0; i < len(batch); i++ { + select { + case result := <-resultCh: + if result.success != nil { + // Success! Cancel other attempts and return + cancelBatch() + // Drain remaining results to avoid goroutine leaks + go func() { + for j := i + 1; j < len(batch); j++ { + <-resultCh + } + }() + return result.success, nil + } + + // Log failure + sn := batch[result.idx] + t.LogEvent(ctx, event.SDKDownloadFailure, "download from super-node failed", event.EventData{ + event.KeySupernode: sn.GrpcEndpoint, + event.KeySupernodeAddress: sn.CosmosAddress, + event.KeyIteration: baseIteration + result.idx + 1, + event.KeyError: result.err.Error(), + }) + errors = append(errors, result.err) + + case <-ctx.Done(): + return nil, []error{ctx.Err()} + } + } + + // All attempts in this batch failed + return nil, errors +} diff --git a/sdk/task/helpers.go b/sdk/task/helpers.go index 1646a326..44c9a662 100644 --- a/sdk/task/helpers.go +++ b/sdk/task/helpers.go @@ -4,6 +4,8 @@ import ( "context" "encoding/base64" "fmt" + "path/filepath" + "strings" "github.com/LumeraProtocol/supernode/sdk/adapters/lumera" ) @@ -99,3 +101,19 @@ func (m *ManagerImpl) validateDownloadAction(ctx context.Context, actionID strin return action, nil } + +// Helper function to ensure output path has the correct filename +func ensureOutputPathWithFilename(outputPath, filename string) string { + // If outputPath is empty, just return the filename + if outputPath == "" { + return filename + } + + // Check if the path already ends with the filename + if strings.HasSuffix(outputPath, filename) { + return outputPath + } + + // Otherwise, append the filename to the path + return filepath.Join(outputPath, filename) +} diff --git a/sdk/task/manager.go b/sdk/task/manager.go index 1f1f0a19..fc25cf30 100644 --- a/sdk/task/manager.go +++ b/sdk/task/manager.go @@ -3,6 +3,7 @@ package task import ( "context" "fmt" + "path" "github.com/LumeraProtocol/supernode/sdk/adapters/lumera" "github.com/LumeraProtocol/supernode/sdk/config" @@ -45,9 +46,6 @@ func NewManager(ctx context.Context, config config.Config, logger log.Logger) (M logger.Info(ctx, "Initializing task manager") - // 2 - Event bus - eventBus := event.NewBus(ctx, logger, MAX_EVENT_WORKERS) - // 3 - Create the Lumera client adapter clientAdapter, err := lumera.NewAdapter(ctx, lumera.ConfigParams{ @@ -62,6 +60,20 @@ func NewManager(ctx context.Context, config config.Config, logger log.Logger) (M panic(fmt.Sprintf("Failed to create Lumera client: %v", err)) } + return NewManagerWithLumeraClient(ctx, config, logger, clientAdapter) +} + +func NewManagerWithLumeraClient(ctx context.Context, config config.Config, logger log.Logger, lumeraClient lumera.Client) (Manager, error) { + // 1 - Logger + if logger == nil { + logger = log.NewNoopLogger() + } + + logger.Info(ctx, "Initializing task manager with provided lumera client") + + // 2 - Event bus + eventBus := event.NewBus(ctx, logger, MAX_EVENT_WORKERS) + taskCache, err := NewTaskCache(ctx, logger) if err != nil { logger.Error(ctx, "Failed to create task cache", "error", err) @@ -69,7 +81,7 @@ func NewManager(ctx context.Context, config config.Config, logger log.Logger) (M } return &ManagerImpl{ - lumeraClient: clientAdapter, + lumeraClient: lumeraClient, config: config, taskCache: taskCache, eventBus: eventBus, @@ -229,21 +241,30 @@ func (m *ManagerImpl) Close(ctx context.Context) { } } -func (m *ManagerImpl) CreateDownloadTask( - ctx context.Context, - actionID string, - outputPath string, -) (string, error) { - +func (m *ManagerImpl) CreateDownloadTask(ctx context.Context, actionID string, outputDir string) (string, error) { // First validate the action before creating the task action, err := m.validateDownloadAction(ctx, actionID) if err != nil { return "", err } + // Decode metadata to get the filename + metadata, err := m.lumeraClient.DecodeCascadeMetadata(ctx, action) + if err != nil { + return "", fmt.Errorf("failed to decode cascade metadata: %w", err) + } + + // Ensure we have a filename from metadata + if metadata.FileName == "" { + return "", fmt.Errorf("no filename found in cascade metadata") + } + + // Ensure the output path includes the correct filename + finalOutputPath := path.Join(outputDir, action.ID, metadata.FileName) + taskID := uuid.New().String()[:8] - m.logger.Debug(ctx, "Generated download task ID", "task_id", taskID) + m.logger.Debug(ctx, "Generated download task ID", "task_id", taskID, "final_output_path", finalOutputPath) baseTask := BaseTask{ TaskID: taskID, @@ -257,7 +278,8 @@ func (m *ManagerImpl) CreateDownloadTask( logger: m.logger, } - task := NewCascadeDownloadTask(baseTask, actionID, outputPath) + // Use the final output path with the correct filename + task := NewCascadeDownloadTask(baseTask, actionID, finalOutputPath) // Store task in cache m.taskCache.Set(ctx, taskID, task, TaskTypeCascade, actionID) @@ -275,6 +297,6 @@ func (m *ManagerImpl) CreateDownloadTask( } }() - m.logger.Info(ctx, "Download Cascade task created successfully", "taskID", taskID) + m.logger.Info(ctx, "Download Cascade task created successfully", "taskID", taskID, "outputPath", finalOutputPath) return taskID, nil } diff --git a/supernode/services/cascade/healthcheck.go b/supernode/services/cascade/healthcheck.go index d9e33122..422494fe 100644 --- a/supernode/services/cascade/healthcheck.go +++ b/supernode/services/cascade/healthcheck.go @@ -3,7 +3,6 @@ package cascade import ( "context" "fmt" - "log" "time" "github.com/LumeraProtocol/supernode/pkg/logtrace" @@ -36,12 +35,17 @@ func (task *CascadeRegistrationTask) HealthCheck(ctx context.Context) (HealthChe percentages, err := cpu.Percent(time.Second, false) if err != nil { - log.Fatal(err) + logtrace.Error(ctx, "failed to get cpu info", logtrace.Fields{logtrace.FieldError: err.Error()}) + return resp, err } fmt.Println(percentages) usage := percentages[0] remaining := 100 - usage + // Set CPU values in response + resp.CPU.Usage = fmt.Sprintf("%.2f", usage) + resp.CPU.Remaining = fmt.Sprintf("%.2f", remaining) + // Memory stats vmem, err := mem.VirtualMemory() if err != nil { @@ -58,14 +62,7 @@ func (task *CascadeRegistrationTask) HealthCheck(ctx context.Context) (HealthChe resp.TasksInProgress = append(resp.TasksInProgress, t.ID()) } - logtrace.Info(ctx, "top-style healthcheck data", logtrace.Fields{ - "cpu_usage": fmt.Sprintf("%.2f", usage), - "cpu_remaining": fmt.Sprintf("%.2f", remaining), - "mem_total": resp.Memory.Total, - "mem_used": resp.Memory.Used, - "mem_used%": resp.Memory.UsedPerc, - "task_count": len(resp.TasksInProgress), - }) + logtrace.Info(ctx, "top-style healthcheck data", logtrace.Fields{"cpu_usage": fmt.Sprintf("%.2f", usage), "cpu_remaining": fmt.Sprintf("%.2f", remaining), "mem_total": resp.Memory.Total, "mem_used": resp.Memory.Used, "mem_used%": resp.Memory.UsedPerc, "task_count": len(resp.TasksInProgress)}) return resp, nil } diff --git a/supernode/services/cascade/healthcheck_test.go b/supernode/services/cascade/healthcheck_test.go index 4c2b3b9b..973136d5 100644 --- a/supernode/services/cascade/healthcheck_test.go +++ b/supernode/services/cascade/healthcheck_test.go @@ -72,6 +72,10 @@ func TestHealthCheck(t *testing.T) { assert.NoError(t, err) + // CPU checks + assert.NotEmpty(t, resp.CPU.Usage) + assert.NotEmpty(t, resp.CPU.Remaining) + // Memory checks assert.True(t, resp.Memory.Total > 0) assert.True(t, resp.Memory.Used <= resp.Memory.Total) diff --git a/tests/scripts/install-lumera.sh b/tests/scripts/install-lumera.sh index 4c657e82..d3c5773c 100755 --- a/tests/scripts/install-lumera.sh +++ b/tests/scripts/install-lumera.sh @@ -5,17 +5,40 @@ set -e # Exit immediately if a command exits with a non-zero status # ./install-lumera.sh # uses latest release # ./install-lumera.sh latest-tag # uses latest tag from /tags # ./install-lumera.sh v1.1.0 # installs this specific version +# LUMERAD_BINARY=/path/to/binary ./install-lumera.sh # uses existing binary -# Support mode argument: 'latest-release' (default) or 'latest-tag' +install_binary() { + local binary_path="$1" + chmod +x "$binary_path" + sudo cp "$binary_path" /usr/local/bin/lumerad + + # Verify installation + if which lumerad > /dev/null; then + echo "Installed: $(lumerad version 2>/dev/null || echo "unknown version")" + else + echo "Installation failed" + exit 1 + fi +} + +# Check if binary path is provided via environment variable +if [ -n "$LUMERAD_BINARY" ]; then + if [ ! -f "$LUMERAD_BINARY" ]; then + echo "Binary not found: $LUMERAD_BINARY" + exit 1 + fi + install_binary "$LUMERAD_BINARY" + exit 0 +fi + +# Support mode argument: 'latest-release' (default), 'latest-tag', or specific version MODE="${1:-latest-release}" REPO="LumeraProtocol/lumera" GITHUB_API="https://api.github.com/repos/$REPO" -echo "Installation mode: $MODE" - +# Determine tag and download URL based on mode if [ "$MODE" == "latest-tag" ]; then - echo "Fetching latest tag from GitHub..." if command -v jq >/dev/null 2>&1; then TAG_NAME=$(curl -s "$GITHUB_API/tags" | jq -r '.[0].name') else @@ -28,7 +51,6 @@ elif [[ "$MODE" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then DOWNLOAD_URL="https://github.com/${REPO}/releases/download/${TAG_NAME}/lumera_${TAG_NAME}_linux_amd64.tar.gz" elif [ "$MODE" == "latest-release" ]; then - echo "Fetching latest release information..." RELEASE_INFO=$(curl -s -S -L "$GITHUB_API/releases/latest") # Extract tag name and download URL @@ -41,14 +63,15 @@ elif [ "$MODE" == "latest-release" ]; then fi else - echo "❌ Error: Invalid mode '$MODE'" + echo "Error: Invalid mode '$MODE'" echo "Usage: $0 [latest-release|latest-tag|vX.Y.Z]" + echo " or: LUMERAD_BINARY=/path/to/binary $0" exit 1 fi echo "Selected tag: $TAG_NAME" -echo "Download URL: $DOWNLOAD_URL" +# Validate that we have the required information if [ -z "$TAG_NAME" ] || [ -z "$DOWNLOAD_URL" ]; then echo "Error: Could not determine tag or download URL" exit 1 @@ -57,8 +80,8 @@ fi # Download and extract the release TEMP_DIR=$(mktemp -d) ORIG_DIR=$(pwd) -echo "Downloading Lumera from $DOWNLOAD_URL" curl -L --progress-bar "$DOWNLOAD_URL" -o "$TEMP_DIR/lumera.tar.gz" + cd "$TEMP_DIR" tar -xzf lumera.tar.gz rm lumera.tar.gz @@ -66,24 +89,13 @@ rm lumera.tar.gz # Install WASM library WASM_LIB=$(find . -type f -name "libwasmvm*.so" -print -quit) if [ -n "$WASM_LIB" ]; then - echo "Installing WASM library: $WASM_LIB" sudo cp "$WASM_LIB" /usr/lib/ fi # Find and install lumerad binary LUMERAD_PATH=$(find . -type f -name "lumerad" -print -quit) if [ -n "$LUMERAD_PATH" ]; then - echo "Installing lumerad binary from: $LUMERAD_PATH" - chmod +x "$LUMERAD_PATH" - sudo cp "$LUMERAD_PATH" /usr/local/bin/ - - # Verify installation - if which lumerad > /dev/null; then - echo "Installation successful. Lumerad version: $(lumerad version 2>/dev/null || echo "unknown")" - else - echo "Error: Lumerad installation failed" - exit 1 - fi + install_binary "$LUMERAD_PATH" else echo "Error: Could not find lumerad binary in the package" exit 1 @@ -91,5 +103,4 @@ fi # Clean up cd "$ORIG_DIR" -rm -rf "$TEMP_DIR" -echo "Lumera installation complete" \ No newline at end of file +rm -rf "$TEMP_DIR" \ No newline at end of file diff --git a/tests/system/e2e_cascade_test.go b/tests/system/e2e_cascade_test.go index 6402c6ad..1181e039 100644 --- a/tests/system/e2e_cascade_test.go +++ b/tests/system/e2e_cascade_test.go @@ -238,7 +238,7 @@ func TestCascadeE2E(t *testing.T) { ) require.NoError(t, err, "Failed to create Lumera client configuration") - lumeraClinet, err := lumera.NewClient(ctx, lumeraCfg) + lumeraClinet, err := lumera.NewClient(context.Background(), lumeraCfg) require.NoError(t, err, "Failed to initialize Lumera client") // --------------------------------------- @@ -360,7 +360,7 @@ func TestCascadeE2E(t *testing.T) { // Wait for transaction to be included in a block sut.AwaitNextBlock(t) - + time.Sleep(5 * time.Second) // Allow some time for the transaction to be processed // Verify the account can be queried with its public key //accountResp := cli.CustomQuery("q", "auth", "account", userAddress) //require.Contains(t, accountResp, "public_key", "User account public key should be available") @@ -406,8 +406,6 @@ func TestCascadeE2E(t *testing.T) { require.NotEmpty(t, actionID, "Action ID should not be empty") t.Logf("Extracted action ID: %s", actionID) - time.Sleep(60 * time.Second) - // Set up action client configuration // This defines how to connect to network services accConfig := sdkconfig.AccountConfig{ @@ -427,7 +425,7 @@ func TestCascadeE2E(t *testing.T) { // Initialize action client for cascade operations actionClient, err := action.NewClient( - ctx, + context.Background(), actionConfig, nil, // Nil logger - use default @@ -443,7 +441,7 @@ func TestCascadeE2E(t *testing.T) { completionCh := make(chan bool, 1) // Subscribe to ALL events - err = actionClient.SubscribeToAllEvents(ctx, func(ctx context.Context, e event.Event) { + err = actionClient.SubscribeToAllEvents(context.Background(), func(ctx context.Context, e event.Event) { // Only capture TxhasReceived events if e.Type == event.SDKTaskTxHashReceived { if txHash, ok := e.Data[event.KeyTxHash].(string); ok && txHash != "" { @@ -560,17 +558,54 @@ func TestCascadeE2E(t *testing.T) { t.Log("Test completed successfully!") - time.Sleep(1 * time.Minute) + time.Sleep(10 * time.Second) - outputFileName := "output.txt" - outputFileFullpath := filepath.Join(t.TempDir(), outputFileName) + outputFileBaseDir := filepath.Join(".") // Try to download the file using the action ID - dtaskID, err := actionClient.DownloadCascade(ctx, actionID, outputFileFullpath) + dtaskID, err := actionClient.DownloadCascade(context.Background(), actionID, outputFileBaseDir) t.Logf("Download response: %s", dtaskID) require.NoError(t, err, "Failed to download cascade data using action ID") - time.Sleep(30 * time.Second) // Wait to ensure all events are processed + time.Sleep(10 * time.Second) // Wait to ensure all events are processed + + // --------------------------------------- + // Step 11: Validate downloaded files exist + // --------------------------------------- + t.Log("Step 11: Validating downloaded files exist in expected directory structure") + + // Construct expected directory path: baseDir/{actionID}/ + expectedDownloadDir := filepath.Join(outputFileBaseDir, actionID) + t.Logf("Checking for files in directory: %s", expectedDownloadDir) + + // Check if the action directory exists + if _, err := os.Stat(expectedDownloadDir); os.IsNotExist(err) { + t.Fatalf("Expected download directory does not exist: %s", expectedDownloadDir) + } + + // Read directory contents + files, err := os.ReadDir(expectedDownloadDir) + require.NoError(t, err, "Failed to read download directory: %s", expectedDownloadDir) + + // Filter out directories, only count actual files + fileCount := 0 + var fileNames []string + for _, file := range files { + if !file.IsDir() { + fileCount++ + fileNames = append(fileNames, file.Name()) + } + } + + t.Logf("Found %d files in download directory: %v", fileCount, fileNames) + + // Validate that at least one file was downloaded + require.True(t, fileCount >= 1, "Expected at least 1 file in download directory %s, found %d files", expectedDownloadDir, fileCount) + + status, err := actionClient.GetSupernodeStatus(ctx, "lumera1cjyc4ruq739e2lakuhargejjkr0q5vg6x3d7kp") + t.Logf("Supernode status: %+v", status) + require.NoError(t, err, "Failed to get supernode status") + } func Blake3Hash(msg []byte) ([]byte, error) { hasher := blake3.New(32, nil) diff --git a/tests/system/go.mod b/tests/system/go.mod index 58ee56c4..4f9ee576 100644 --- a/tests/system/go.mod +++ b/tests/system/go.mod @@ -27,7 +27,7 @@ require ( require ( cosmossdk.io/math v1.5.3 - github.com/LumeraProtocol/lumera v1.5.0 + github.com/LumeraProtocol/lumera v1.6.0 github.com/LumeraProtocol/supernode v0.0.0-00010101000000-000000000000 github.com/cometbft/cometbft v0.38.17 github.com/tidwall/gjson v1.14.2 @@ -125,7 +125,6 @@ require ( github.com/linxGnu/grocksdb v1.9.8 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect github.com/minio/highwayhash v1.0.3 // indirect github.com/mtibben/percent v0.2.1 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect @@ -143,7 +142,6 @@ require ( github.com/rs/zerolog v1.34.0 // indirect github.com/sagikazarmark/locafero v0.7.0 // indirect github.com/sasha-s/go-deadlock v0.3.5 // indirect - github.com/sirupsen/logrus v1.9.3 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.12.0 // indirect github.com/spf13/viper v1.20.1 // indirect @@ -153,7 +151,6 @@ require ( github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect - github.com/x-cray/logrus-prefixed-formatter v0.5.2 // indirect github.com/zondax/hid v0.9.2 // indirect github.com/zondax/ledger-go v0.14.3 // indirect go.etcd.io/bbolt v1.4.0-alpha.0.0.20240404170359-43604f3112c5 // indirect @@ -170,7 +167,6 @@ require ( google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250422160041-2d3770c4ea7f // indirect google.golang.org/protobuf v1.36.6 // indirect - gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gotest.tools/v3 v3.5.2 // indirect nhooyr.io/websocket v1.8.10 // indirect diff --git a/tests/system/go.sum b/tests/system/go.sum index 395110bf..ba702050 100644 --- a/tests/system/go.sum +++ b/tests/system/go.sum @@ -73,8 +73,8 @@ github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48 github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.48.1 h1:8nn+rsCvTq9axyEh382S0PFLBeaFwNsT43IrPWzctRU= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.48.1/go.mod h1:viRWSEhtMZqz1rhwmOVKkWl6SwmVowfL9O2YR5gI2PE= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= -github.com/LumeraProtocol/lumera v1.5.0 h1:LDPtd155PjG/LKk34x/3vhC9H+J9tHoxwrcwRMG6jzM= -github.com/LumeraProtocol/lumera v1.5.0/go.mod h1:c1M+sjewuCvxw+pznwlspUzenDJI8Y+suKB3RFKS2Wo= +github.com/LumeraProtocol/lumera v1.6.0 h1:5I172U/f1Migt7tRxnywhz5aRKCpBOx/IMgOzhJfTP0= +github.com/LumeraProtocol/lumera v1.6.0/go.mod h1:c1M+sjewuCvxw+pznwlspUzenDJI8Y+suKB3RFKS2Wo= github.com/LumeraProtocol/rq-go v0.2.1 h1:8B3UzRChLsGMmvZ+UVbJsJj6JZzL9P9iYxbdUwGsQI4= github.com/LumeraProtocol/rq-go v0.2.1/go.mod h1:APnKCZRh1Es2Vtrd2w4kCLgAyaL5Bqrkz/BURoRJ+O8= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= @@ -563,8 +563,6 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= -github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/minio/highwayhash v1.0.3 h1:kbnuUMoHYyVl7szWjSxJnxw11k2U709jqFPPmIUyD6Q= github.com/minio/highwayhash v1.0.3/go.mod h1:GGYsuwP/fPD6Y9hMiXuapVvlIUEhFhMTh0rxU3ik1LQ= @@ -802,8 +800,6 @@ github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8= github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/x-cray/logrus-prefixed-formatter v0.5.2 h1:00txxvfBM9muc0jiLIEAkAcIMJzfthRT6usrui8uGmg= -github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -983,7 +979,6 @@ golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1103,8 +1098,6 @@ gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qS gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= -gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= -gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=