From 2bf8b9bc410c661a7e69165e8635121f4d4d9d90 Mon Sep 17 00:00:00 2001 From: Matee ullah Malik Date: Mon, 5 May 2025 18:27:18 +0500 Subject: [PATCH 1/2] Fixes --- pkg/lumera/modules/action_msg/impl.go | 66 ++++++++++++++++---------- supernode/services/cascade/register.go | 3 +- tests/system/cli.go | 3 +- tests/system/e2e_cascade_test.go | 30 +++++++++--- tests/system/system.go | 8 ++-- 5 files changed, 70 insertions(+), 40 deletions(-) diff --git a/pkg/lumera/modules/action_msg/impl.go b/pkg/lumera/modules/action_msg/impl.go index 45caad11..98ddb7b8 100644 --- a/pkg/lumera/modules/action_msg/impl.go +++ b/pkg/lumera/modules/action_msg/impl.go @@ -3,6 +3,7 @@ package action_msg import ( "context" "fmt" + "strconv" actionapi "github.com/LumeraProtocol/lumera/api/lumera/action" actiontypes "github.com/LumeraProtocol/lumera/x/action/types" @@ -22,13 +23,13 @@ import ( "google.golang.org/protobuf/encoding/protojson" ) -// Default gas parameters +// Default parameters const ( defaultGasLimit = uint64(200000) - defaultMinGasLimit = uint64(100000) - defaultMaxGasLimit = uint64(1000000) - defaultGasAdjustment = float64(3.0) + defaultGasAdjustment = float64(1.5) defaultGasPadding = uint64(50000) + defaultFeeDenom = "ulume" + defaultGasPrice = "0.000001" // Price per unit of gas ) // module implements the Module interface @@ -39,10 +40,10 @@ type module struct { keyName string chainID string gasLimit uint64 - minGasLimit uint64 - maxGasLimit uint64 gasAdjustment float64 gasPadding uint64 + feeDenom string + gasPrice string } // newModule creates a new ActionMsg module client @@ -70,13 +71,26 @@ func newModule(conn *grpc.ClientConn, kr keyring.Keyring, keyName string, chainI keyName: keyName, chainID: chainID, gasLimit: defaultGasLimit, - minGasLimit: defaultMinGasLimit, - maxGasLimit: defaultMaxGasLimit, gasAdjustment: defaultGasAdjustment, gasPadding: defaultGasPadding, + feeDenom: defaultFeeDenom, + gasPrice: defaultGasPrice, }, nil } +// calculateFee calculates the transaction fee based on gas usage +func (m *module) calculateFee(gasAmount uint64) string { + gasPrice, _ := strconv.ParseFloat(m.gasPrice, 64) + feeAmount := gasPrice * float64(gasAmount) + + // Ensure we have at least 1 token as fee to meet minimum requirements + if feeAmount < 1 { + feeAmount = 1 + } + + return fmt.Sprintf("%.0f%s", feeAmount, m.feeDenom) +} + // FinalizeCascadeAction finalizes a CASCADE action with the given parameters func (m *module) FinalizeCascadeAction( ctx context.Context, @@ -142,6 +156,9 @@ func (m *module) FinalizeCascadeAction( WithKeyring(m.kr). WithBroadcastMode("sync") + // Use a minimal fee for simulation + minFee := fmt.Sprintf("1%s", m.feeDenom) + // Simulate transaction to get gas estimate txBuilder, err := tx.Factory{}. WithTxConfig(clientCtx.TxConfig). @@ -152,36 +169,41 @@ func (m *module) FinalizeCascadeAction( WithGas(m.gasLimit). WithGasAdjustment(m.gasAdjustment). WithSignMode(signingtypes.SignMode_SIGN_MODE_DIRECT). - WithFees("1000stake"). + WithFees(minFee). BuildUnsignedTx(msg) if err != nil { return nil, fmt.Errorf("failed to build unsigned tx for simulation: %w", err) } + pubKey, err := key.GetPubKey() if err != nil { return nil, fmt.Errorf("failed to get public key: %w", err) } + txBuilder.SetSignatures(signingtypes.SignatureV2{ - PubKey: pubKey, // your signing pubkey + PubKey: pubKey, Data: &signingtypes.SingleSignatureData{SignMode: signingtypes.SignMode_SIGN_MODE_DIRECT, Signature: nil}, Sequence: accInfo.Sequence, }) + simulatedGas, err := m.simulateTx(ctx, clientCtx, txBuilder) if err != nil { return nil, fmt.Errorf("simulation failed: %w", err) } + // Calculate gas with adjustment and padding adjustedGas := uint64(float64(simulatedGas) * m.gasAdjustment) gasToUse := adjustedGas + m.gasPadding - // Apply gas bounds - if gasToUse > m.maxGasLimit { - gasToUse = m.maxGasLimit - } - - logtrace.Info(ctx, "using simulated gas", logtrace.Fields{"simulatedGas": simulatedGas, "adjustedGas": gasToUse}) + // Calculate fee based on adjusted gas + fee := m.calculateFee(gasToUse) + logtrace.Info(ctx, "using simulated gas and calculated fee", logtrace.Fields{ + "simulatedGas": simulatedGas, + "adjustedGas": gasToUse, + "fee": fee, + }) - // Create transaction factory with final gas + // Create transaction factory with final gas and calculated fee factory := tx.Factory{}. WithTxConfig(clientCtx.TxConfig). WithKeybase(m.kr). @@ -190,7 +212,8 @@ func (m *module) FinalizeCascadeAction( WithChainID(m.chainID). WithGas(gasToUse). WithGasAdjustment(m.gasAdjustment). - WithSignMode(signingtypes.SignMode_SIGN_MODE_DIRECT) + WithSignMode(signingtypes.SignMode_SIGN_MODE_DIRECT). + WithFees(fee) // Build and sign transaction txBuilder, err = factory.BuildUnsignedTx(msg) @@ -255,13 +278,6 @@ func (m *module) simulateTx(ctx context.Context, clientCtx client.Context, txBui TxBytes: txBytes, } - // Check if we have the tx in the request too - if simReq.Tx != nil { - logtrace.Info(ctx, "simulation request has tx field", logtrace.Fields{ - "txFieldPresent": true, - }) - } - logtrace.Info(ctx, "sending simulation request", logtrace.Fields{ "requestBytes": len(simReq.TxBytes), "requestType": fmt.Sprintf("%T", simReq), diff --git a/supernode/services/cascade/register.go b/supernode/services/cascade/register.go index d81d49c9..fd108506 100644 --- a/supernode/services/cascade/register.go +++ b/supernode/services/cascade/register.go @@ -99,8 +99,7 @@ func (task *CascadeRegistrationTask) Register(ctx context.Context, req *Register logtrace.Info(ctx, "Finalize Action Error", logtrace.Fields{ "error": err.Error(), }) - - return &RegisterResponse{Success: true, Message: "successfully uploaded input data"}, nil + return nil, err } logtrace.Info(ctx, "Finalize Action Response", logtrace.Fields{ diff --git a/tests/system/cli.go b/tests/system/cli.go index 351156c1..9e42dd8c 100644 --- a/tests/system/cli.go +++ b/tests/system/cli.go @@ -20,7 +20,6 @@ import ( codectypes "github.com/cosmos/cosmos-sdk/codec/types" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" "github.com/cosmos/cosmos-sdk/std" - sdk "github.com/cosmos/cosmos-sdk/types" ) type ( @@ -40,7 +39,7 @@ func NewLumeradCLI(t *testing.T, sut *SystemUnderTest, verbose bool) *LumeradCli sut.AwaitNextBlock, sut.nodesCount, filepath.Join(WorkDir, sut.outputDir), - "1"+sdk.DefaultBondDenom, + "1"+"ulume", verbose, assert.NoError, true, diff --git a/tests/system/e2e_cascade_test.go b/tests/system/e2e_cascade_test.go index 0935849b..5b96e596 100644 --- a/tests/system/e2e_cascade_test.go +++ b/tests/system/e2e_cascade_test.go @@ -10,6 +10,7 @@ import ( "os" "os/exec" "path/filepath" + "strconv" "strings" "testing" "time" @@ -112,11 +113,12 @@ func TestCascadeE2E(t *testing.T) { registerSupernode("node0", "4444", "lumera1em87kgrvgttrkvuamtetyaagjrhnu3vjy44at4") registerSupernode("node1", "4446", "lumera1cf0ms9ttgdvz6zwlqfty4tjcawhuaq69p40w0c") registerSupernode("node2", "4448", "lumera1cjyc4ruq739e2lakuhargejjkr0q5vg6x3d7kp") + t.Log("Successfully registered three supernodes") + // Fund Lume cli.FundAddress("lumera1em87kgrvgttrkvuamtetyaagjrhnu3vjy44at4", "100000ulume") cli.FundAddressWithNode("lumera1cf0ms9ttgdvz6zwlqfty4tjcawhuaq69p40w0c", "100000ulume", "node1") cli.FundAddressWithNode("lumera1cjyc4ruq739e2lakuhargejjkr0q5vg6x3d7kp", "100000ulume", "node2") - t.Log("Successfully registered three supernodes") queryHeight := sut.AwaitNextBlock(t) args := []string{ @@ -190,12 +192,6 @@ func TestCascadeE2E(t *testing.T) { "Local keyring address should match expected address") t.Logf("Successfully recovered key in local keyring with matching address: %s", localAddr.String()) - // Verify account has sufficient balance for transactions - balanceOutput := cli.CustomQuery("query", "bank", "balances", recoveredAddress) - t.Logf("Balance for account: %s", balanceOutput) - require.Contains(t, balanceOutput, fundAmount[:len(fundAmount)-5], - "Account should have the funded amount") - // Initialize Lumera blockchain client for interactions // @@ -448,6 +444,26 @@ func TestCascadeE2E(t *testing.T) { t.Logf("Task recorded %d events", eventCount) require.Greater(t, eventCount, 0, "Task should have recorded events") + // ---------------------------------- + // Step 11: Verify fee deduction + // ---------------------------------- + t.Log("Step 11: Verifying fee deduction") + + // Query final balance + finalBalance := cli.QueryBalance(recoveredAddress, "ulume") + t.Logf("Final balance: %d ulume", finalBalance) + + // Parse initial fund amount and expected price + initialFundAmount, err := strconv.ParseInt(strings.TrimSuffix(fundAmount, "ulume"), 10, 64) + require.NoError(t, err, "Failed to parse fund amount") + expectedPriceFee, err := strconv.ParseInt(strings.TrimSuffix(price, "ulume"), 10, 64) + require.NoError(t, err, "Failed to parse price") + + // Verify fee deduction + t.Logf("Initial fund: %d ulume, Action price: %d ulume", initialFundAmount, expectedPriceFee) + require.Less(t, finalBalance, initialFundAmount, "Fee should have been deducted from creator's account") + t.Logf("Verified fee deduction: account balance (%d) is less than initial fund (%d)", finalBalance, initialFundAmount) + } func Blake3Hash(msg []byte) ([]byte, error) { diff --git a/tests/system/system.go b/tests/system/system.go index df40fda9..1c37ef02 100644 --- a/tests/system/system.go +++ b/tests/system/system.go @@ -26,7 +26,6 @@ import ( "github.com/tidwall/sjson" "github.com/cosmos/cosmos-sdk/server" - sdk "github.com/cosmos/cosmos-sdk/types" ) var ( @@ -90,9 +89,10 @@ func NewSystemUnderTest(execBinary string, verbose bool, nodesCount int, blockTi errBuff: ring.New(100), out: os.Stdout, verbose: verbose, - minGasPrice: fmt.Sprintf("0.000001%s", sdk.DefaultBondDenom), - projectName: nameTokens[0], - pids: make(map[int]struct{}, nodesCount), + // minGasPrice: fmt.Sprintf("0.000001%s", sdk.DefaultBondDenom), + minGasPrice: fmt.Sprintf("0.000001%s", "ulume"), + projectName: nameTokens[0], + pids: make(map[int]struct{}, nodesCount), } } From f920f6bbf6561eb6a64d74d518a05149717f27b8 Mon Sep 17 00:00:00 2001 From: Matee ullah Malik Date: Tue, 6 May 2025 00:16:10 +0500 Subject: [PATCH 2/2] Add txID --- sdk/event/types.go | 1 + sdk/go.mod | 6 +- sdk/go.sum | 4 +- sdk/task/cascade.go | 18 +++++ supernode/services/cascade/register.go | 3 +- tests/system/e2e_cascade_test.go | 107 ++++++++----------------- 6 files changed, 58 insertions(+), 81 deletions(-) diff --git a/sdk/event/types.go b/sdk/event/types.go index 9adcf330..7f6df100 100644 --- a/sdk/event/types.go +++ b/sdk/event/types.go @@ -23,6 +23,7 @@ const ( TaskProgressRegistrationFailure EventType = "task.progress.registration_failure" TaskProgressRegistrationSuccessful EventType = "task.progress.registration_successful" TaskCompleted EventType = "task.completed" + TxhasReceived EventType = "txhash.received" TaskFailed EventType = "task.failed" ) diff --git a/sdk/go.mod b/sdk/go.mod index a341e068..385964e4 100644 --- a/sdk/go.mod +++ b/sdk/go.mod @@ -1,15 +1,15 @@ module github.com/LumeraProtocol/supernode/sdk -go 1.24.0 +go 1.24.1 replace github.com/LumeraProtocol/supernode => ../ require ( + github.com/LumeraProtocol/lumera v0.4.5 github.com/LumeraProtocol/supernode v0.0.0-00010101000000-000000000000 github.com/cosmos/cosmos-sdk v0.53.0 github.com/dgraph-io/ristretto/v2 v2.2.0 github.com/google/uuid v1.6.0 - golang.org/x/sync v0.13.0 google.golang.org/grpc v1.72.0 ) @@ -29,7 +29,6 @@ require ( github.com/99designs/keyring v1.2.2 // indirect github.com/DataDog/datadog-go v3.2.0+incompatible // indirect github.com/DataDog/zstd v1.5.7 // indirect - github.com/LumeraProtocol/lumera v0.4.4 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bgentry/speakeasy v0.2.0 // indirect github.com/bytedance/sonic v1.13.2 // indirect @@ -150,6 +149,7 @@ require ( golang.org/x/crypto v0.37.0 // indirect golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect golang.org/x/net v0.39.0 // indirect + golang.org/x/sync v0.13.0 // indirect golang.org/x/sys v0.32.0 // indirect golang.org/x/term v0.31.0 // indirect golang.org/x/text v0.24.0 // indirect diff --git a/sdk/go.sum b/sdk/go.sum index 13bbfb46..e13c36b7 100644 --- a/sdk/go.sum +++ b/sdk/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 v0.4.4 h1:jMMyKs5fT5mShdLwVDM2QA+hJ4zNk4Tn0teaKGQSvak= -github.com/LumeraProtocol/lumera v0.4.4/go.mod h1:MRqVY+f8edEBkDvpr4z2nJpglp3Qj1OUvjeWvrvIUSM= +github.com/LumeraProtocol/lumera v0.4.5 h1:eeDeUFMKYAbCKDZVZzPsFpdiypoWsFUD37fd49EyGSE= +github.com/LumeraProtocol/lumera v0.4.5/go.mod h1:c1M+sjewuCvxw+pznwlspUzenDJI8Y+suKB3RFKS2Wo= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE= diff --git a/sdk/task/cascade.go b/sdk/task/cascade.go index 52b7eef7..d666574e 100644 --- a/sdk/task/cascade.go +++ b/sdk/task/cascade.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "strings" "time" "github.com/LumeraProtocol/supernode/sdk/adapters/lumera" @@ -189,6 +190,12 @@ func (t *CascadeTask) attemptRegistration(ctx context.Context, index int, sn lum return fmt.Errorf("upload rejected by %s: %s", sn.CosmosAddress, resp.Message) } + txhash := CleanTxHash(resp.Message) + t.logEvent(ctx, event.TxhasReceived, "txhash received", map[string]interface{}{ + "txhash": txhash, + "supernode": sn.CosmosAddress, + }) + t.logger.Info(ctx, "upload OK", "taskID", t.TaskID, "address", sn.CosmosAddress) return nil } @@ -225,3 +232,14 @@ func (t *CascadeTask) fail(ctx context.Context, failureEvent event.EventType, er return err } + +func CleanTxHash(input string) string { + // Split by colon and get the last part + parts := strings.Split(input, ":") + if len(parts) <= 1 { + return input + } + + // Return the last part with spaces trimmed + return strings.TrimSpace(parts[len(parts)-1]) +} diff --git a/supernode/services/cascade/register.go b/supernode/services/cascade/register.go index fd108506..f6f46542 100644 --- a/supernode/services/cascade/register.go +++ b/supernode/services/cascade/register.go @@ -2,6 +2,7 @@ package cascade import ( "context" + "fmt" "time" "github.com/LumeraProtocol/supernode/pkg/logtrace" @@ -107,5 +108,5 @@ func (task *CascadeRegistrationTask) Register(ctx context.Context, req *Register "log": resp.TxHash}) // Return success when the cascade action is finalized without errors - return &RegisterResponse{Success: true, Message: "successfully uploaded and finalized input data"}, nil + return &RegisterResponse{Success: true, Message: fmt.Sprintf("successfully uploaded and finalized input data with txID: %s", resp.TxHash)}, nil } diff --git a/tests/system/e2e_cascade_test.go b/tests/system/e2e_cascade_test.go index 5b96e596..17653e58 100644 --- a/tests/system/e2e_cascade_test.go +++ b/tests/system/e2e_cascade_test.go @@ -10,7 +10,6 @@ import ( "os" "os/exec" "path/filepath" - "strconv" "strings" "testing" "time" @@ -21,7 +20,6 @@ import ( "github.com/LumeraProtocol/supernode/sdk/action" "github.com/LumeraProtocol/supernode/sdk/event" - "github.com/LumeraProtocol/supernode/sdk/task" "github.com/LumeraProtocol/lumera/x/action/types" sdkconfig "github.com/LumeraProtocol/supernode/sdk/config" @@ -268,7 +266,6 @@ func TestCascadeE2E(t *testing.T) { t.Log("Step 7: Creating metadata and submitting action request") // Create CascadeMetadata struct with all required fields - cascadeMetadata := types.CascadeMetadata{ DataHash: b64EncodedHash, // Hash of the original file FileName: filepath.Base(testFileFullpath), // Original filename @@ -307,7 +304,6 @@ func TestCascadeE2E(t *testing.T) { // Wait for transaction to be included in a block sut.AwaitNextBlock(t) - time.Sleep(5 * time.Second) // Verify the account can be queried with its public key accountResp := cli.CustomQuery("q", "auth", "account", recoveredAddress) @@ -356,7 +352,6 @@ func TestCascadeE2E(t *testing.T) { // Set up action client configuration // This defines how to connect to network services - accConfig := sdkconfig.AccountConfig{ LocalCosmosAddress: recoveredAddress, } @@ -381,91 +376,53 @@ func TestCascadeE2E(t *testing.T) { ) require.NoError(t, err, "Failed to create action client") - // Start cascade operation with all required parameters + // --------------------------------------- + // Step 9: Subscribe to all events and extract tx hash + // --------------------------------------- + + // Channel to receive the transaction hash + txHashCh := make(chan string, 1) + completionCh := make(chan bool, 1) + + // Subscribe to ALL events + err = actionClient.SubscribeToAllEvents(ctx, func(ctx context.Context, e event.Event) { + // Only capture TxhasReceived events + if e.Type == event.TxhasReceived { + if txHash, ok := e.Data["txhash"].(string); ok && txHash != "" { + // Send the hash to our channel + txHashCh <- txHash + } + } + + // Also monitor for task completion + if e.Type == event.TaskCompleted { + completionCh <- true + } + }) + require.NoError(t, err, "Failed to subscribe to events") + // Start cascade operation t.Logf("Starting cascade operation with action ID: %s", actionID) taskID, err := actionClient.StartCascade( ctx, data, // data []byte actionID, // Action ID from the transaction - ) require.NoError(t, err, "Failed to start cascade operation") - require.NotEmpty(t, taskID, "Task ID should not be empty") t.Logf("Cascade operation started with task ID: %s", taskID) - // --------------------------------------- - // Step 9: Monitor task completion - // --------------------------------------- + recievedhash := <-txHashCh + <-completionCh - // Set up event channels for task monitoring - completionCh := make(chan bool) - errorCh := make(chan error) + t.Logf("Received transaction hash: %s", recievedhash) - // Subscribe to task completion events - actionClient.SubscribeToEvents(ctx, event.TaskCompleted, func(ctx context.Context, e event.Event) { - if e.TaskID == taskID { - t.Logf("Task completed: %s", taskID) - completionCh <- true - } - }) + time.Sleep(10 * time.Second) + txReponse := cli.CustomQuery("q", "tx", recievedhash) - // Subscribe to task failure events - actionClient.SubscribeToEvents(ctx, event.TaskFailed, func(ctx context.Context, e event.Event) { - if e.TaskID == taskID { - errorMsg, _ := e.Data["error"].(string) - errorCh <- fmt.Errorf("task failed: %s", errorMsg) - } - }) - - // Wait for task completion, failure, or timeout - t.Log("Waiting for cascade task to complete...") - select { - case <-completionCh: - t.Log("Cascade task completed successfully") - case err := <-errorCh: - t.Fatalf("Cascade task failed: %v", err) - case <-time.After(60 * time.Second): - t.Fatalf("Timeout waiting for cascade task to complete") - } - - // --------------------------------------- - // Step 10: Verify task completion and results - // --------------------------------------- - - // Get the task details to verify status - taskEntry, found := actionClient.GetTask(ctx, taskID) - require.True(t, found, "Task should be found") - require.Equal(t, taskEntry.Status, task.StatusCompleted, "Task should be completed") - t.Logf("Task status: %s", taskEntry.Status) - - // Additional verification based on the events in the task - eventCount := len(taskEntry.Events) - t.Logf("Task recorded %d events", eventCount) - require.Greater(t, eventCount, 0, "Task should have recorded events") - - // ---------------------------------- - // Step 11: Verify fee deduction - // ---------------------------------- - t.Log("Step 11: Verifying fee deduction") - - // Query final balance - finalBalance := cli.QueryBalance(recoveredAddress, "ulume") - t.Logf("Final balance: %d ulume", finalBalance) - - // Parse initial fund amount and expected price - initialFundAmount, err := strconv.ParseInt(strings.TrimSuffix(fundAmount, "ulume"), 10, 64) - require.NoError(t, err, "Failed to parse fund amount") - expectedPriceFee, err := strconv.ParseInt(strings.TrimSuffix(price, "ulume"), 10, 64) - require.NoError(t, err, "Failed to parse price") - - // Verify fee deduction - t.Logf("Initial fund: %d ulume, Action price: %d ulume", initialFundAmount, expectedPriceFee) - require.Less(t, finalBalance, initialFundAmount, "Fee should have been deducted from creator's account") - t.Logf("Verified fee deduction: account balance (%d) is less than initial fund (%d)", finalBalance, initialFundAmount) + t.Logf("Transaction response: %s", txReponse) + // } - func Blake3Hash(msg []byte) ([]byte, error) { hasher := blake3.New(32, nil) if _, err := io.Copy(hasher, bytes.NewReader(msg)); err != nil {