Skip to content

Commit

Permalink
Merge pull request #135 from ElrondNetwork/transaction-sort-logic-2
Browse files Browse the repository at this point in the history
Transaction sort logic 2
  • Loading branch information
iulianpascalau authored Dec 21, 2022
2 parents ca2de25 + 4470c36 commit 6a02727
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 7 deletions.
45 changes: 45 additions & 0 deletions core/transaction/transactionSorter.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,34 @@ func SortTransactionsBySenderAndNonceWithFrontRunningProtection(transactions []d
sort.Slice(transactions, sorter)
}

// TODO remove duplicated function when will use the version of elrond-go which exports transaction order during processing

// SortTransactionsBySenderAndNonceWithFrontRunningProtectionExtendedTransactions - sorts the transactions by address and randomness source to protect from front running
func SortTransactionsBySenderAndNonceWithFrontRunningProtectionExtendedTransactions(transactions []data.TransactionHandlerWithGasUsedAndFee, hasher hashing.Hasher, randomness []byte) {
// make sure randomness is 32bytes and uniform
randSeed := hasher.Compute(string(randomness))
xoredAddresses := make(map[string][]byte)

for _, tx := range transactions {
xoredBytes := xorBytes(tx.GetSndAddr(), randSeed)
xoredAddresses[string(tx.GetSndAddr())] = hasher.Compute(string(xoredBytes))
}

sorter := func(i, j int) bool {
txI := transactions[i]
txJ := transactions[j]

delta := bytes.Compare(xoredAddresses[string(txI.GetSndAddr())], xoredAddresses[string(txJ.GetSndAddr())])
if delta == 0 {
delta = int(txI.GetNonce()) - int(txJ.GetNonce())
}

return delta < 0
}

sort.Slice(transactions, sorter)
}

// SortTransactionsBySenderAndNonce - sorts the transactions by address without the front running protection
func SortTransactionsBySenderAndNonce(transactions []data.TransactionHandler) {
sorter := func(i, j int) bool {
Expand All @@ -51,6 +79,23 @@ func SortTransactionsBySenderAndNonce(transactions []data.TransactionHandler) {
sort.Slice(transactions, sorter)
}

// SortTransactionsBySenderAndNonceExtendedTransactions - sorts the transactions by address without the front running protection
func SortTransactionsBySenderAndNonceExtendedTransactions(transactions []data.TransactionHandlerWithGasUsedAndFee) {
sorter := func(i, j int) bool {
txI := transactions[i]
txJ := transactions[j]

delta := bytes.Compare(txI.GetSndAddr(), txJ.GetSndAddr())
if delta == 0 {
delta = int(txI.GetNonce()) - int(txJ.GetNonce())
}

return delta < 0
}

sort.Slice(transactions, sorter)
}

// parameters need to be of the same len, otherwise it will panic (if second slice shorter)
func xorBytes(a, b []byte) []byte {
res := make([]byte, len(a))
Expand Down
15 changes: 14 additions & 1 deletion core/transaction/transactionSorter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ package transaction
import (
"encoding/hex"
"fmt"
"math/big"
"testing"

"github.com/ElrondNetwork/elrond-go-core/core/mock"
"github.com/ElrondNetwork/elrond-go-core/data"
"github.com/ElrondNetwork/elrond-go-core/data/outport"
"github.com/ElrondNetwork/elrond-go-core/data/transaction"
"github.com/stretchr/testify/assert"
)
Expand Down Expand Up @@ -45,8 +47,13 @@ func Test_SortTransactionsBySenderAndNonceWithFrontRunningProtection(t *testing.
&transaction.Transaction{Nonce: 3, SndAddr: senders[3]},
&transaction.Transaction{Nonce: 3, SndAddr: senders[2]},
}
wrappedTxs := make([]data.TransactionHandlerWithGasUsedAndFee, 0, len(txs))
for _, tx := range txs {
wrappedTxs = append(wrappedTxs, outport.NewTransactionHandlerWithGasAndFee(tx, 0, big.NewInt(0)))
}

SortTransactionsBySenderAndNonceWithFrontRunningProtection(txs, hasher, []byte(randomness))
SortTransactionsBySenderAndNonceWithFrontRunningProtectionExtendedTransactions(wrappedTxs, hasher, []byte(randomness))

expectedOutput := []string{
"1 ffffffffffffffffffffffffffffff00",
Expand All @@ -62,6 +69,7 @@ func Test_SortTransactionsBySenderAndNonceWithFrontRunningProtection(t *testing.

for i, item := range txs {
assert.Equal(t, expectedOutput[i], fmt.Sprintf("%d %s", item.GetNonce(), hex.EncodeToString(item.GetSndAddr())))
assert.Equal(t, expectedOutput[i], fmt.Sprintf("%d %s", wrappedTxs[i].GetNonce(), hex.EncodeToString(wrappedTxs[i].GetSndAddr())))
}
}

Expand All @@ -76,8 +84,13 @@ func Test_SortTransactionsBySenderAndNonceLegacy(t *testing.T) {
&transaction.Transaction{Nonce: 3, SndAddr: []byte("ffff")},
&transaction.Transaction{Nonce: 3, SndAddr: []byte("eeee")},
}
wrappedTxs := make([]data.TransactionHandlerWithGasUsedAndFee, 0, len(txs))
for _, tx := range txs {
wrappedTxs = append(wrappedTxs, outport.NewTransactionHandlerWithGasAndFee(tx, 0, big.NewInt(0)))
}

SortTransactionsBySenderAndNonce(txs)
SortTransactionsBySenderAndNonceExtendedTransactions(wrappedTxs)

expectedOutput := []string{
"1 aaaa",
Expand All @@ -92,6 +105,6 @@ func Test_SortTransactionsBySenderAndNonceLegacy(t *testing.T) {

for i, item := range txs {
assert.Equal(t, expectedOutput[i], fmt.Sprintf("%d %s", item.GetNonce(), string(item.GetSndAddr())))
assert.Equal(t, expectedOutput[i], fmt.Sprintf("%d %s", wrappedTxs[i].GetNonce(), string(wrappedTxs[i].GetSndAddr())))
}

}
2 changes: 2 additions & 0 deletions data/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,8 @@ type TransactionHandlerWithGasUsedAndFee interface {
GetGasUsed() uint64
GetFee() *big.Int
GetTxHandler() TransactionHandler
SetExecutionOrder(order int)
GetExecutionOrder() int
}

// LogHandler defines the type for a log resulted from executing a transaction or smart contract call
Expand Down
14 changes: 8 additions & 6 deletions data/outport/dtos.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,14 @@ type HeaderGasConsumption struct {

// Pool will hold all types of transaction
type Pool struct {
Txs map[string]data.TransactionHandlerWithGasUsedAndFee
Scrs map[string]data.TransactionHandlerWithGasUsedAndFee
Rewards map[string]data.TransactionHandlerWithGasUsedAndFee
Invalid map[string]data.TransactionHandlerWithGasUsedAndFee
Receipts map[string]data.TransactionHandlerWithGasUsedAndFee
Logs []*data.LogData
Txs map[string]data.TransactionHandlerWithGasUsedAndFee
Scrs map[string]data.TransactionHandlerWithGasUsedAndFee
Rewards map[string]data.TransactionHandlerWithGasUsedAndFee
Invalid map[string]data.TransactionHandlerWithGasUsedAndFee
Receipts map[string]data.TransactionHandlerWithGasUsedAndFee
Logs []*data.LogData
ScheduledExecutedSCRSHashesPrevBlock []string
ScheduledExecutedInvalidTxsHashesPrevBlock []string
}

// ValidatorRatingInfo is a structure containing validator rating information
Expand Down
11 changes: 11 additions & 0 deletions data/outport/txWithFee.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type FeeInfo struct {
type TransactionHandlerWithGasAndFee struct {
data.TransactionHandler
FeeInfo
ExecutionOrder int
}

// NewTransactionHandlerWithGasAndFee returns a new instance of transactionHandlerWithGasAndFee which matches the interface
Expand Down Expand Up @@ -65,6 +66,16 @@ func (t *TransactionHandlerWithGasAndFee) GetTxHandler() data.TransactionHandler
return t.TransactionHandler
}

// SetExecutionOrder will set the execution order of the TransactionHandler
func (t *TransactionHandlerWithGasAndFee) SetExecutionOrder(order int) {
t.ExecutionOrder = order
}

// GetExecutionOrder will return the execution order of the TransactionHandler
func (t *TransactionHandlerWithGasAndFee) GetExecutionOrder() int {
return t.ExecutionOrder
}

// WrapTxsMap will wrap the provided transactions map in a map fo transactions with fee and gas used
func WrapTxsMap(txs map[string]data.TransactionHandler) map[string]data.TransactionHandlerWithGasUsedAndFee {
newMap := make(map[string]data.TransactionHandlerWithGasUsedAndFee, len(txs))
Expand Down

0 comments on commit 6a02727

Please sign in to comment.