From f8450e19c42b42dd10b6e8d3d2dba7762427a69f Mon Sep 17 00:00:00 2001 From: milad Date: Fri, 8 Mar 2024 17:00:18 +0330 Subject: [PATCH] Add stress tests with failures and refunds --- .../stress/sending_stress_test.go | 236 ++++++++++++++++++ 1 file changed, 236 insertions(+) diff --git a/integration-tests/stress/sending_stress_test.go b/integration-tests/stress/sending_stress_test.go index 0fe0996b..39d13de5 100644 --- a/integration-tests/stress/sending_stress_test.go +++ b/integration-tests/stress/sending_stress_test.go @@ -5,18 +5,21 @@ package stress_test import ( "context" + "math" "strconv" "testing" "time" sdkmath "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" "github.com/pkg/errors" rippledata "github.com/rubblelabs/ripple/data" "github.com/stretchr/testify/require" "github.com/CoreumFoundation/coreum-tools/pkg/parallel" "github.com/CoreumFoundation/coreum-tools/pkg/retry" + "github.com/CoreumFoundation/coreum/v4/pkg/client" coreumintegration "github.com/CoreumFoundation/coreum/v4/testutil/integration" integrationtests "github.com/CoreumFoundation/coreumbridge-xrpl/integration-tests" bridgeclient "github.com/CoreumFoundation/coreumbridge-xrpl/relayer/client" @@ -165,6 +168,217 @@ func TestStressSendFromXRPLToCoreumAndBack(t *testing.T) { t.Logf("Ran %d Operations in %s, %s per operation", testCount, testDuration, testDuration/time.Duration(testCount)) } +func TestStressSendWithFailureAndClaimRefund(t *testing.T) { + _, chains := integrationtests.NewTestingContext(t) + testAccounts := 10 + iterationPerAccount := 5 + testCount := testAccounts * iterationPerAccount + ctx, cancel := context.WithTimeout(context.Background(), time.Minute*time.Duration(testCount)) + t.Cleanup(cancel) + + contractClient := coreum.NewContractClient( + coreum.DefaultContractClientConfig(integrationtests.GetContractAddress(t)), + chains.Log, + chains.Coreum.ClientContext, + ) + bankClient := banktypes.NewQueryClient(chains.Coreum.ClientContext) + + xrplTxSigner := xrpl.NewKeyringTxSigner(chains.XRPL.GetSignerKeyring()) + bridgeClient := bridgeclient.NewBridgeClient( + chains.Log, + chains.Coreum.ClientContext, + contractClient, + chains.XRPL.RPCClient(), + xrplTxSigner, + ) + + sendAmount := 1 + valueToSendFromXRPLtoCoreum, err := rippledata.NewNativeValue(int64(sendAmount)) + require.NoError(t, err) + + registeredXRPToken, err := contractClient.GetXRPLTokenByIssuerAndCurrency( + ctx, xrpl.XRPTokenIssuer.String(), xrpl.ConvertCurrencyToString(xrpl.XRPTokenCurrency), + ) + // generate and fund accounts + type xrplAccount struct { + Acccount rippledata.Account + Exists bool + } + xrplAccounts := make([]xrplAccount, 0) + coreumAccounts := make([]sdk.AccAddress, 0) + + t.Log("Generating and funding accounts") + for i := 0; i < testAccounts; i++ { + newCoreumAccount := chains.Coreum.GenAccount() + coreumAccounts = append(coreumAccounts, newCoreumAccount) + chains.Coreum.FundAccountWithOptions(ctx, t, newCoreumAccount, coreumintegration.BalancesOptions{ + Amount: sdkmath.NewIntFromUint64(500_000 * uint64(iterationPerAccount)), + }) + var newXRPLAccount xrplAccount + // every 1 in 5 accounts should be empty to simulate failure. + if i%5 == 0 { + newXRPLAccount = xrplAccount{chains.XRPL.GenEmptyAccount(t), false} + } else { + newXRPLAccount = xrplAccount{chains.XRPL.GenAccount(ctx, t, 0), true} + } + xrplAccounts = append(xrplAccounts, newXRPLAccount) + } + + fundCoreumAccountsWithXRP( + ctx, + t, + chains, + *bridgeClient, + registeredXRPToken.CoreumDenom, + coreumAccounts, + xrpValueMulRaw(t, valueToSendFromXRPLtoCoreum, int64(iterationPerAccount)), + ) + + t.Log("Accounts generated and funded") + + err = parallel.Run(ctx, func(ctx context.Context, spawn parallel.SpawnFn) error { + for i := 0; i < testAccounts; i++ { + coreumAccount := coreumAccounts[i] + xrplAccount := xrplAccounts[i] + // accounts start with 10 initial xrp balance. + expectedBalance, err := rippledata.NewValue("10000000", true) + require.NoError(t, err) + spawn(strconv.Itoa(i), parallel.Continue, func(ctx context.Context) error { + for j := 0; j < iterationPerAccount; j++ { + err = bridgeClient.SendFromCoreumToXRPL( + ctx, + coreumAccount, + xrplAccount.Acccount, + sdk.NewCoin( + registeredXRPToken.CoreumDenom, + integrationtests.ConvertStringWithDecimalsToSDKInt( + t, + valueToSendFromXRPLtoCoreum.String(), + xrpl.XRPCurrencyDecimals, + )), + nil, + ) + if err != nil { + return err + } + + if xrplAccount.Exists { + expectedBalance, err = expectedBalance.Add(*valueToSendFromXRPLtoCoreum) + if err != nil { + return err + } + waitCtx, waitCancel := context.WithTimeout(ctx, 10*time.Second) + defer waitCancel() + awaitXRPLBalance( + waitCtx, + t, + chains.XRPL, + xrplAccount.Acccount, + xrpl.XRPTokenIssuer, + xrpl.XRPTokenCurrency, + *expectedBalance, + ) + } else { + waitCtx, waitCancel := context.WithTimeout(ctx, 10*time.Second) + defer waitCancel() + refunds := awaitPendingRefund(waitCtx, t, contractClient, coreumAccount) + require.Len(t, refunds, 1) + balanceBefore, err := bankClient.Balance(ctx, banktypes.NewQueryBalanceRequest(coreumAccount, registeredXRPToken.CoreumDenom)) + require.NoError(t, err) + _, err = contractClient.ClaimRefund(ctx, coreumAccount, refunds[0].ID) + require.NoError(t, err) + balanceAfter, err := bankClient.Balance(ctx, banktypes.NewQueryBalanceRequest(coreumAccount, registeredXRPToken.CoreumDenom)) + require.NoError(t, err) + balanceChange := balanceAfter.GetBalance().Amount.Sub(balanceBefore.Balance.Amount) + require.EqualValues(t, balanceChange.Int64(), sendAmount) + } + } + return nil + }) + } + return nil + }) + require.NoError(t, err) +} + +func xrpValueMulRaw(t *testing.T, rValue *rippledata.Value, n int64) *rippledata.Value { + var nValue *rippledata.Value + var err error + if rValue.IsNative() { + nValue, err = rippledata.NewNativeValue(n) + require.NoError(t, err) + } else { + nValue, err = rippledata.NewNonNativeValue(n, 0) + require.NoError(t, err) + } + res, err := rValue.Multiply(*nValue) + require.NoError(t, err) + return res +} + +func fundCoreumAccountsWithXRP( + ctx context.Context, + t *testing.T, + chains integrationtests.Chains, + bridgeClient bridgeclient.BridgeClient, + registeredXrpDenomOnCoreum string, + coreumAccounts []sdk.AccAddress, + xrpToEachAccount *rippledata.Value, +) { + coreumAccount := chains.Coreum.GenAccount() + totalSendValue := xrpValueMulRaw(t, xrpToEachAccount, int64(len(coreumAccounts))) + xrplAccount := chains.XRPL.GenAccount(ctx, t, totalSendValue.Float()) + xrpAmount := rippledata.Amount{ + Value: totalSendValue, + Currency: xrpl.XRPTokenCurrency, + Issuer: xrpl.XRPTokenIssuer, + } + err := bridgeClient.SendFromXRPLToCoreum( + ctx, xrplAccount.String(), xrpAmount, coreumAccount, + ) + require.NoError(t, err) + err = chains.Coreum.AwaitForBalance( + ctx, + t, + coreumAccount, + sdk.NewCoin( + registeredXrpDenomOnCoreum, + integrationtests.ConvertStringWithDecimalsToSDKInt( + t, + totalSendValue.String(), + xrpl.XRPCurrencyDecimals, + )), + ) + require.NoError(t, err) + + sdkIntAmount := sdkmath.NewInt(int64(math.Ceil(xrpToEachAccount.Float() * 1_000_000))) + msg := &banktypes.MsgMultiSend{ + Inputs: []banktypes.Input{{ + Address: coreumAccount.String(), + Coins: sdk.NewCoins(sdk.NewCoin(registeredXrpDenomOnCoreum, sdkIntAmount.MulRaw(int64(len(coreumAccounts))))), + }}, + Outputs: []banktypes.Output{}, + } + for _, acc := range coreumAccounts { + acc := acc + msg.Outputs = append(msg.Outputs, banktypes.Output{ + Address: acc.String(), + Coins: sdk.NewCoins(sdk.NewCoin(registeredXrpDenomOnCoreum, sdkIntAmount)), + }) + } + chains.Coreum.FundAccountWithOptions(ctx, t, coreumAccount, coreumintegration.BalancesOptions{ + Messages: []sdk.Msg{msg}, + }) + + _, err = client.BroadcastTx( + ctx, + chains.Coreum.ClientContext.WithFromAddress(coreumAccount), + chains.Coreum.TxFactory().WithSimulateAndExecute(true), + msg, + ) + require.NoError(t, err) +} + func awaitXRPLBalance( ctx context.Context, t *testing.T, @@ -184,6 +398,28 @@ func awaitXRPLBalance( }) } +func awaitPendingRefund( + ctx context.Context, + t *testing.T, + contractClient *coreum.ContractClient, + account sdk.AccAddress, +) []coreum.PendingRefund { + t.Helper() + var refunds []coreum.PendingRefund + awaitState(ctx, t, func(t *testing.T) error { + var err error + refunds, err = contractClient.GetPendingRefunds(ctx, account) + if err != nil { + return err + } + if len(refunds) == 0 { + return errors.Errorf("no pending refunds for address %s", account.String()) + } + return nil + }) + return refunds +} + func awaitState(ctx context.Context, t *testing.T, stateChecker func(t *testing.T) error) { t.Helper() err := retry.Do(ctx, 500*time.Millisecond, func() error {