From 853f67604809c0122cf381f59c72b633f4fef9ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Thu, 12 Dec 2024 14:28:33 +0200 Subject: [PATCH] Add / improve tests. --- testscommon/txcachemocks/mempoolHostMock.go | 29 +++-- txcache/selectionSessionWrapper_test.go | 133 ++++++++++++++++++++ txcache/testutils_test.go | 6 + txcache/wrappedTransaction_test.go | 13 +- 4 files changed, 161 insertions(+), 20 deletions(-) create mode 100644 txcache/selectionSessionWrapper_test.go diff --git a/testscommon/txcachemocks/mempoolHostMock.go b/testscommon/txcachemocks/mempoolHostMock.go index d8d797b0..e90d7115 100644 --- a/testscommon/txcachemocks/mempoolHostMock.go +++ b/testscommon/txcachemocks/mempoolHostMock.go @@ -9,10 +9,12 @@ import ( // MempoolHostMock - type MempoolHostMock struct { - minGasLimit uint64 - minGasPrice uint64 - gasPerDataByte uint64 - gasPriceModifier float64 + minGasLimit uint64 + minGasPrice uint64 + gasPerDataByte uint64 + gasPriceModifier float64 + extraGasLimitForGuarded uint64 + extraGasLimitForRelayed uint64 ComputeTxFeeCalled func(tx data.TransactionWithFeeHandler) *big.Int GetTransferredValueCalled func(tx data.TransactionHandler) *big.Int @@ -21,10 +23,12 @@ type MempoolHostMock struct { // NewMempoolHostMock - func NewMempoolHostMock() *MempoolHostMock { return &MempoolHostMock{ - minGasLimit: 50000, - minGasPrice: 1000000000, - gasPerDataByte: 1500, - gasPriceModifier: 0.01, + minGasLimit: 50_000, + minGasPrice: 1_000_000_000, + gasPerDataByte: 1500, + gasPriceModifier: 0.01, + extraGasLimitForGuarded: 50_000, + extraGasLimitForRelayed: 50_000, } } @@ -39,6 +43,15 @@ func (mock *MempoolHostMock) ComputeTxFee(tx data.TransactionWithFeeHandler) *bi gasPriceForProcessing := uint64(float64(gasPriceForMovement) * mock.gasPriceModifier) gasLimitForMovement := mock.minGasLimit + dataLength*mock.gasPerDataByte + + if txAsGuarded, ok := tx.(data.GuardedTransactionHandler); ok && len(txAsGuarded.GetGuardianAddr()) > 0 { + gasLimitForMovement += mock.extraGasLimitForGuarded + } + + if txAsRelayed, ok := tx.(data.RelayedTransactionHandler); ok && len(txAsRelayed.GetRelayerAddr()) > 0 { + gasLimitForMovement += mock.extraGasLimitForRelayed + } + if tx.GetGasLimit() < gasLimitForMovement { panic("tx.GetGasLimit() < gasLimitForMovement") } diff --git a/txcache/selectionSessionWrapper_test.go b/txcache/selectionSessionWrapper_test.go new file mode 100644 index 00000000..24aa9775 --- /dev/null +++ b/txcache/selectionSessionWrapper_test.go @@ -0,0 +1,133 @@ +package txcache + +import ( + "math/big" + "testing" + + "github.com/multiversx/mx-chain-storage-go/testscommon/txcachemocks" + "github.com/stretchr/testify/require" +) + +func TestSelectionSessionWrapper_accumulateConsumedBalance(t *testing.T) { + host := txcachemocks.NewMempoolHostMock() + + t.Run("when sender is fee payer", func(t *testing.T) { + session := txcachemocks.NewSelectionSessionMock() + sessionWrapper := newSelectionSessionWrapper(session) + + a := createTx([]byte("a-7"), "a", 7) + b := createTx([]byte("a-8"), "a", 8).withValue(oneQuintillionBig) + + a.precomputeFields(host) + b.precomputeFields(host) + + sessionWrapper.accumulateConsumedBalance(a) + require.Equal(t, "50000000000000", sessionWrapper.getAccountRecord([]byte("a")).consumedBalance.String()) + + sessionWrapper.accumulateConsumedBalance(b) + require.Equal(t, "1000100000000000000", sessionWrapper.getAccountRecord([]byte("a")).consumedBalance.String()) + }) + + t.Run("when relayer is fee payer", func(t *testing.T) { + session := txcachemocks.NewSelectionSessionMock() + sessionWrapper := newSelectionSessionWrapper(session) + + a := createTx([]byte("a-7"), "a", 7).withRelayer([]byte("b")).withGasLimit(100_000) + b := createTx([]byte("a-8"), "a", 8).withValue(oneQuintillionBig).withRelayer([]byte("b")).withGasLimit(100_000) + + a.precomputeFields(host) + b.precomputeFields(host) + + sessionWrapper.accumulateConsumedBalance(a) + require.Equal(t, "0", sessionWrapper.getAccountRecord([]byte("a")).consumedBalance.String()) + require.Equal(t, "100000000000000", sessionWrapper.getAccountRecord([]byte("b")).consumedBalance.String()) + + sessionWrapper.accumulateConsumedBalance(b) + require.Equal(t, "1000000000000000000", sessionWrapper.getAccountRecord([]byte("a")).consumedBalance.String()) + require.Equal(t, "200000000000000", sessionWrapper.getAccountRecord([]byte("b")).consumedBalance.String()) + }) +} + +func TestSelectionSessionWrapper_detectWillFeeExceedBalance(t *testing.T) { + host := txcachemocks.NewMempoolHostMock() + + t.Run("unknown", func(t *testing.T) { + session := txcachemocks.NewSelectionSessionMock() + sessionWrapper := newSelectionSessionWrapper(session) + + a := createTx([]byte("tx-1"), "alice", 42) + a.precomputeFields(host) + + require.False(t, sessionWrapper.detectWillFeeExceedBalance(a)) + }) + + t.Run("will not exceed for (a) and (b), but will exceed for (c)", func(t *testing.T) { + a := createTx([]byte("tx-1"), "alice", 42) + b := createTx([]byte("tx-2"), "alice", 43).withValue(oneQuintillionBig) + c := createTx([]byte("tx-3"), "alice", 44).withValue(oneQuintillionBig) + + a.precomputeFields(host) + b.precomputeFields(host) + c.precomputeFields(host) + + session := txcachemocks.NewSelectionSessionMock() + sessionWrapper := newSelectionSessionWrapper(session) + + session.SetBalance([]byte("alice"), big.NewInt(oneQuintillion+50000000000000+1)) + recordAlice := sessionWrapper.getAccountRecord([]byte("alice")) + require.Equal(t, "0", recordAlice.consumedBalance.String()) + require.Equal(t, "1000050000000000001", recordAlice.initialBalance.String()) + + require.False(t, sessionWrapper.detectWillFeeExceedBalance(a)) + + sessionWrapper.accumulateConsumedBalance(a) + + require.Equal(t, "50000000000000", recordAlice.consumedBalance.String()) + + // Even though, in reality, that will be an invalid (but executable) transaction (insufficient balance). + require.False(t, sessionWrapper.detectWillFeeExceedBalance(b)) + + sessionWrapper.accumulateConsumedBalance(b) + + require.Equal(t, "1000100000000000000", recordAlice.consumedBalance.String()) + require.True(t, sessionWrapper.detectWillFeeExceedBalance(c)) + }) + + t.Run("will not exceed for (a) and (b), but will exceed for (c) (with relayed)", func(t *testing.T) { + a := createTx([]byte("tx-1"), "alice", 42).withRelayer([]byte("carol")).withGasLimit(100_000) + b := createTx([]byte("tx-2"), "alice", 43).withValue(oneQuintillionBig).withRelayer([]byte("carol")).withGasLimit(100_000) + c := createTx([]byte("tx-3"), "alice", 44).withValue(oneQuintillionBig).withRelayer([]byte("carol")).withGasLimit(100_000) + + a.precomputeFields(host) + b.precomputeFields(host) + c.precomputeFields(host) + + session := txcachemocks.NewSelectionSessionMock() + sessionWrapper := newSelectionSessionWrapper(session) + + session.SetBalance([]byte("alice"), big.NewInt(oneQuintillion)) + session.SetBalance([]byte("carol"), big.NewInt(100000000000000+100000000000000+1)) + recordAlice := sessionWrapper.getAccountRecord([]byte("alice")) + recordCarol := sessionWrapper.getAccountRecord([]byte("carol")) + require.Equal(t, "0", recordAlice.consumedBalance.String()) + require.Equal(t, "1000000000000000000", recordAlice.initialBalance.String()) + require.Equal(t, "0", recordCarol.consumedBalance.String()) + require.Equal(t, "200000000000001", recordCarol.initialBalance.String()) + + require.False(t, sessionWrapper.detectWillFeeExceedBalance(a)) + + sessionWrapper.accumulateConsumedBalance(a) + + require.Equal(t, "0", recordAlice.consumedBalance.String()) + require.Equal(t, "100000000000000", recordCarol.consumedBalance.String()) + + require.False(t, sessionWrapper.detectWillFeeExceedBalance(b)) + + sessionWrapper.accumulateConsumedBalance(b) + + require.Equal(t, "1000000000000000000", recordAlice.consumedBalance.String()) + require.Equal(t, "200000000000000", recordCarol.consumedBalance.String()) + require.True(t, sessionWrapper.detectWillFeeExceedBalance(c)) + }) +} + diff --git a/txcache/testutils_test.go b/txcache/testutils_test.go index 49dc0e85..388789d1 100644 --- a/txcache/testutils_test.go +++ b/txcache/testutils_test.go @@ -192,6 +192,12 @@ func (wrappedTx *WrappedTransaction) withValue(value *big.Int) *WrappedTransacti return wrappedTx } +func (wrappedTx *WrappedTransaction) withRelayer(relayer []byte) *WrappedTransaction { + tx := wrappedTx.Tx.(*transaction.Transaction) + tx.RelayerAddr = relayer + return wrappedTx +} + func createFakeSenderAddress(senderTag int) []byte { bytes := make([]byte, 32) binary.LittleEndian.PutUint64(bytes, uint64(senderTag)) diff --git a/txcache/wrappedTransaction_test.go b/txcache/wrappedTransaction_test.go index 5a595e7d..49c0c3ed 100644 --- a/txcache/wrappedTransaction_test.go +++ b/txcache/wrappedTransaction_test.go @@ -5,7 +5,6 @@ import ( "testing" "github.com/multiversx/mx-chain-core-go/data" - "github.com/multiversx/mx-chain-core-go/data/transaction" "github.com/multiversx/mx-chain-storage-go/testscommon/txcachemocks" "github.com/stretchr/testify/require" ) @@ -98,17 +97,7 @@ func TestWrappedTransaction_decideFeePayer(t *testing.T) { }) t.Run("when relayer is fee payer", func(t *testing.T) { - tx := &WrappedTransaction{ - Tx: &transaction.Transaction{ - SndAddr: []byte("a"), - Nonce: 7, - GasLimit: 1_000_000, - GasPrice: oneBillion, - RelayerAddr: []byte("b"), - }, - TxHash: []byte("abba"), - } - + tx := createTx([]byte("a"), "a", 1).withRelayer([]byte("b")).withGasLimit(100_000) tx.precomputeFields(host) require.Nil(t, tx.TransferredValue)