Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mempool optimization: on transaction addition, call "GetTransferredValue" and retain its value #6641

Merged
merged 3 commits into from
Nov 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion dataRetriever/factory/dataPoolFactory.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,10 @@ func NewDataPoolFromConfig(args ArgsDataPool) (dataRetriever.PoolsHolder, error)

txPool, err := txpool.NewShardedTxPool(txpool.ArgShardedTxPool{
Config: factory.GetCacherFromConfig(mainConfig.TxDataPool),
TxGasHandler: args.EconomicsData,
Marshalizer: args.Marshalizer,
NumberOfShards: args.ShardCoordinator.NumberOfShards(),
SelfShardID: args.ShardCoordinator.SelfId(),
TxGasHandler: args.EconomicsData,
})
if err != nil {
return nil, fmt.Errorf("%w while creating the cache for the transactions", err)
Expand Down
8 changes: 6 additions & 2 deletions dataRetriever/txpool/argShardedTxPool.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,16 @@ import (
"fmt"

"github.com/multiversx/mx-chain-core-go/core/check"
"github.com/multiversx/mx-chain-core-go/marshal"
"github.com/multiversx/mx-chain-go/dataRetriever"
"github.com/multiversx/mx-chain-go/storage/storageunit"
"github.com/multiversx/mx-chain-go/storage/txcache"
)

// ArgShardedTxPool is the argument for ShardedTxPool's constructor
type ArgShardedTxPool struct {
Config storageunit.CacheConfig
TxGasHandler txcache.TxGasHandler
TxGasHandler txGasHandler
Marshalizer marshal.Marshalizer
NumberOfShards uint32
SelfShardID uint32
}
Expand All @@ -39,6 +40,9 @@ func (args *ArgShardedTxPool) verify() error {
if check.IfNil(args.TxGasHandler) {
return fmt.Errorf("%w: TxGasHandler is not valid", dataRetriever.ErrNilTxGasHandler)
}
if check.IfNil(args.Marshalizer) {
return fmt.Errorf("%w: Marshalizer is not valid", dataRetriever.ErrNilMarshalizer)
}
if args.NumberOfShards == 0 {
return fmt.Errorf("%w: NumberOfShards is not valid", dataRetriever.ErrCacheConfigInvalidSharding)
}
Expand Down
8 changes: 8 additions & 0 deletions dataRetriever/txpool/interface.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package txpool

import (
"math/big"

"github.com/multiversx/mx-chain-core-go/data"
"github.com/multiversx/mx-chain-go/storage"
"github.com/multiversx/mx-chain-go/storage/txcache"
)
Expand All @@ -17,3 +20,8 @@ type txCache interface {
Diagnose(deep bool)
GetTransactionsPoolForSender(sender string) []*txcache.WrappedTransaction
}

type txGasHandler interface {
ComputeTxFee(tx data.TransactionWithFeeHandler) *big.Int
IsInterfaceNil() bool
}
2 changes: 2 additions & 0 deletions dataRetriever/txpool/memorytests/memory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (

"github.com/multiversx/mx-chain-core-go/core"
"github.com/multiversx/mx-chain-core-go/data/transaction"
"github.com/multiversx/mx-chain-core-go/marshal"
"github.com/multiversx/mx-chain-go/dataRetriever"
"github.com/multiversx/mx-chain-go/dataRetriever/txpool"
"github.com/multiversx/mx-chain-go/storage/storageunit"
Expand Down Expand Up @@ -112,6 +113,7 @@ func newPool() dataRetriever.ShardedDataCacherNotifier {
args := txpool.ArgShardedTxPool{
Config: config,
TxGasHandler: txcachemocks.NewTxGasHandlerMock(),
Marshalizer: &marshal.GogoProtoMarshalizer{},
NumberOfShards: 2,
SelfShardID: 0,
}
Expand Down
112 changes: 112 additions & 0 deletions dataRetriever/txpool/mempoolHost.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package txpool

import (
"bytes"
"math/big"

"github.com/multiversx/mx-chain-core-go/core"
"github.com/multiversx/mx-chain-core-go/core/check"
"github.com/multiversx/mx-chain-core-go/data"
"github.com/multiversx/mx-chain-core-go/marshal"
"github.com/multiversx/mx-chain-go/dataRetriever"
"github.com/multiversx/mx-chain-go/process"
vmcommon "github.com/multiversx/mx-chain-vm-common-go"
"github.com/multiversx/mx-chain-vm-common-go/parsers"
)

type argsMempoolHost struct {
txGasHandler txGasHandler
marshalizer marshal.Marshalizer
}

type mempoolHost struct {
txGasHandler txGasHandler
callArgumentsParser process.CallArgumentsParser
esdtTransferParser vmcommon.ESDTTransferParser
}

func newMempoolHost(args argsMempoolHost) (*mempoolHost, error) {
if check.IfNil(args.txGasHandler) {
return nil, dataRetriever.ErrNilTxGasHandler
}
if check.IfNil(args.marshalizer) {
return nil, dataRetriever.ErrNilMarshalizer
}
Comment on lines +29 to +34
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Somehow superfluous checks (dependencies are verified one layer above, as well), should I remove them?


argsParser := parsers.NewCallArgsParser()

esdtTransferParser, err := parsers.NewESDTTransferParser(args.marshalizer)
Comment on lines +36 to +38
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not get these two as arguments as well?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We instantiate them within mempoolHost, so that shardedTxPool doesn't have knowledge on the components from VM common parsers - somehow, a bit of information hiding (even though both mempoolHost and shardedTxPool live in the same package).

if err != nil {
return nil, err
}

return &mempoolHost{
txGasHandler: args.txGasHandler,
callArgumentsParser: argsParser,
esdtTransferParser: esdtTransferParser,
}, nil
}

// ComputeTxFee computes the fee for a transaction.
func (host *mempoolHost) ComputeTxFee(tx data.TransactionWithFeeHandler) *big.Int {
return host.txGasHandler.ComputeTxFee(tx)
}

// GetTransferredValue returns the value transferred by a transaction.
func (host *mempoolHost) GetTransferredValue(tx data.TransactionHandler) *big.Int {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function moved from SelectionSession, no other change.

value := tx.GetValue()
hasValue := value != nil && value.Sign() != 0
if hasValue {
// Early exit (optimization): a transaction can either bear a regular value or be a "MultiESDTNFTTransfer".
return value
}

data := tx.GetData()
hasData := len(data) > 0
if !hasData {
// Early exit (optimization): no "MultiESDTNFTTransfer" to parse.
return tx.GetValue()
}

maybeMultiTransfer := bytes.HasPrefix(data, []byte(core.BuiltInFunctionMultiESDTNFTTransfer))
if !maybeMultiTransfer {
// Early exit (optimization).
return big.NewInt(0)
}

function, args, err := host.callArgumentsParser.ParseData(string(data))
if err != nil {
return big.NewInt(0)
}

if function != core.BuiltInFunctionMultiESDTNFTTransfer {
// Early exit (optimization).
return big.NewInt(0)
}

esdtTransfers, err := host.esdtTransferParser.ParseESDTTransfers(tx.GetSndAddr(), tx.GetRcvAddr(), function, args)
if err != nil {
return big.NewInt(0)
}

accumulatedNativeValue := big.NewInt(0)

for _, transfer := range esdtTransfers.ESDTTransfers {
if transfer.ESDTTokenNonce != 0 {
continue
}
if string(transfer.ESDTTokenName) != vmcommon.EGLDIdentifier {
// We only care about native transfers.
continue
}

_ = accumulatedNativeValue.Add(accumulatedNativeValue, transfer.ESDTValue)
}

return accumulatedNativeValue
}

// IsInterfaceNil returns true if there is no value under the interface
func (host *mempoolHost) IsInterfaceNil() bool {
return host == nil
}
182 changes: 182 additions & 0 deletions dataRetriever/txpool/mempoolHost_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
package txpool

import (
"encoding/hex"
"fmt"
"math/big"
"testing"

"github.com/multiversx/mx-chain-core-go/core"
"github.com/multiversx/mx-chain-core-go/data/transaction"
"github.com/multiversx/mx-chain-core-go/marshal"
"github.com/multiversx/mx-chain-go/dataRetriever"
"github.com/multiversx/mx-chain-go/testscommon"
"github.com/multiversx/mx-chain-go/testscommon/txcachemocks"
"github.com/stretchr/testify/require"
)

func TestNewMempoolHost(t *testing.T) {
t.Parallel()

host, err := newMempoolHost(argsMempoolHost{
txGasHandler: nil,
marshalizer: &marshal.GogoProtoMarshalizer{},
})
require.Nil(t, host)
require.ErrorIs(t, err, dataRetriever.ErrNilTxGasHandler)

host, err = newMempoolHost(argsMempoolHost{
txGasHandler: txcachemocks.NewTxGasHandlerMock(),
marshalizer: nil,
})
require.Nil(t, host)
require.ErrorIs(t, err, dataRetriever.ErrNilMarshalizer)

host, err = newMempoolHost(argsMempoolHost{
txGasHandler: txcachemocks.NewTxGasHandlerMock(),
marshalizer: &marshal.GogoProtoMarshalizer{},
})
require.NoError(t, err)
require.NotNil(t, host)
}

func TestMempoolHost_GetTransferredValue(t *testing.T) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tests simply moved from selectionSession_test.go.

t.Parallel()

host, err := newMempoolHost(argsMempoolHost{
txGasHandler: txcachemocks.NewTxGasHandlerMock(),
marshalizer: &marshal.GogoProtoMarshalizer{},
})
require.NoError(t, err)
require.NotNil(t, host)

t.Run("with value", func(t *testing.T) {
value := host.GetTransferredValue(&transaction.Transaction{
Value: big.NewInt(1000000000000000000),
})
require.Equal(t, big.NewInt(1000000000000000000), value)
})

t.Run("with value and data", func(t *testing.T) {
value := host.GetTransferredValue(&transaction.Transaction{
Value: big.NewInt(1000000000000000000),
Data: []byte("data"),
})
require.Equal(t, big.NewInt(1000000000000000000), value)
})

t.Run("native transfer within MultiESDTNFTTransfer", func(t *testing.T) {
value := host.GetTransferredValue(&transaction.Transaction{
SndAddr: testscommon.TestPubKeyAlice,
RcvAddr: testscommon.TestPubKeyAlice,
Data: []byte("MultiESDTNFTTransfer@8049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f8@03@4e46542d313233343536@0a@01@544553542d393837363534@01@01@45474c442d303030303030@@0de0b6b3a7640000"),
})
require.Equal(t, big.NewInt(1000000000000000000), value)
})

t.Run("native transfer within MultiESDTNFTTransfer; transfer & execute", func(t *testing.T) {
value := host.GetTransferredValue(&transaction.Transaction{
SndAddr: testscommon.TestPubKeyAlice,
RcvAddr: testscommon.TestPubKeyAlice,
Data: []byte("MultiESDTNFTTransfer@00000000000000000500b9353fe8407f87310c87e12fa1ac807f0485da39d152@03@4e46542d313233343536@01@01@4e46542d313233343536@2a@01@45474c442d303030303030@@0de0b6b3a7640000@64756d6d79@07"),
})
require.Equal(t, big.NewInt(1000000000000000000), value)
})
}

func TestBenchmarkMempoolHost_GetTransferredValue(t *testing.T) {
host, err := newMempoolHost(argsMempoolHost{
txGasHandler: txcachemocks.NewTxGasHandlerMock(),
marshalizer: &marshal.GogoProtoMarshalizer{},
})
require.NoError(t, err)
require.NotNil(t, host)

sw := core.NewStopWatch()

valueMultiplier := int64(1_000_000_000_000)

t.Run("numTransactions = 5_000", func(t *testing.T) {
numTransactions := 5_000
transactions := createMultiESDTNFTTransfersWithNativeTransfer(numTransactions, valueMultiplier)

sw.Start(t.Name())

for i := 0; i < numTransactions; i++ {
tx := transactions[i]
value := host.GetTransferredValue(tx)
require.Equal(t, big.NewInt(int64(i)*valueMultiplier), value)
}

sw.Stop(t.Name())
})

t.Run("numTransactions = 10_000", func(t *testing.T) {
numTransactions := 10_000
transactions := createMultiESDTNFTTransfersWithNativeTransfer(numTransactions, valueMultiplier)

sw.Start(t.Name())

for i := 0; i < numTransactions; i++ {
tx := transactions[i]
value := host.GetTransferredValue(tx)
require.Equal(t, big.NewInt(int64(i)*valueMultiplier), value)
}

sw.Stop(t.Name())
})

t.Run("numTransactions = 20_000", func(t *testing.T) {
numTransactions := 20_000
transactions := createMultiESDTNFTTransfersWithNativeTransfer(numTransactions, valueMultiplier)

sw.Start(t.Name())

for i := 0; i < numTransactions; i++ {
tx := transactions[i]
value := host.GetTransferredValue(tx)
require.Equal(t, big.NewInt(int64(i)*valueMultiplier), value)
}

sw.Stop(t.Name())
})

for name, measurement := range sw.GetMeasurementsMap() {
fmt.Printf("%fs (%s)\n", measurement, name)
}

// (1)
// Vendor ID: GenuineIntel
// Model name: 11th Gen Intel(R) Core(TM) i7-1165G7 @ 2.80GHz
// CPU family: 6
// Model: 140
// Thread(s) per core: 2
// Core(s) per socket: 4
//
// NOTE: 20% is also due to the require() / assert() calls.
// 0.012993s (TestBenchmarkMempoolHost_GetTransferredValue/numTransactions_=_5_000)
// 0.024580s (TestBenchmarkMempoolHost_GetTransferredValue/numTransactions_=_10_000)
// 0.048808s (TestBenchmarkMempoolHost_GetTransferredValue/numTransactions_=_20_000)
}

func createMultiESDTNFTTransfersWithNativeTransfer(numTransactions int, valueMultiplier int64) []*transaction.Transaction {
transactions := make([]*transaction.Transaction, 0, numTransactions)

for i := 0; i < numTransactions; i++ {
nativeValue := big.NewInt(int64(i) * valueMultiplier)
data := fmt.Sprintf(
"MultiESDTNFTTransfer@8049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f8@03@4e46542d313233343536@0a@01@544553542d393837363534@01@01@45474c442d303030303030@@%s",
hex.EncodeToString(nativeValue.Bytes()),
)

tx := &transaction.Transaction{
SndAddr: testscommon.TestPubKeyAlice,
RcvAddr: testscommon.TestPubKeyAlice,
Data: []byte(data),
}

transactions = append(transactions, tx)
}

return transactions
}
Loading