-
Notifications
You must be signed in to change notification settings - Fork 204
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
Mempool optimization: on transaction addition, call "GetTransferredValue" and retain its value #6641
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 | ||
} | ||
|
||
argsParser := parsers.NewCallArgsParser() | ||
|
||
esdtTransferParser, err := parsers.NewESDTTransferParser(args.marshalizer) | ||
Comment on lines
+36
to
+38
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why not get these two as arguments as well? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We instantiate them within |
||
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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Function moved from |
||
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 | ||
} |
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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Tests simply moved from |
||
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 | ||
} |
There was a problem hiding this comment.
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?